From 45fcf636e834e6db13ebc7a9d8d74c61bd939b1b Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 16 May 2016 14:09:16 +1000 Subject: [PATCH 001/723] New script: dwigradcheck The intent of this script is to determine, in a data-driven fashion, whether or not the diffusion gradient table is incorrectly oriented. This is the first primitive working version: additional command-line options, gradient corruption tests, and output options, are still required. Is related to #534. --- cmd/tckstats.cpp | 67 ++++++++++++---- scripts/dwigradcheck | 154 ++++++++++++++++++++++++++++++++++++ scripts/lib/app.py | 44 ++++++----- scripts/lib/citations.py | 1 + scripts/lib/getTrackStat.py | 13 +++ scripts/lib/runCommand.py | 8 +- 6 files changed, 247 insertions(+), 40 deletions(-) create mode 100755 scripts/dwigradcheck create mode 100644 scripts/lib/getTrackStat.py diff --git a/cmd/tckstats.cpp b/cmd/tckstats.cpp index 0c61f36935..3e6560081f 100644 --- a/cmd/tckstats.cpp +++ b/cmd/tckstats.cpp @@ -38,11 +38,17 @@ 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 }; + void usage () { - AUTHOR = "Robert E. Smith (r.smith@brain.org.au)"; + AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; DESCRIPTION + "calculate statistics on streamlines length."; @@ -51,12 +57,20 @@ void usage () + 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 ("dump", "dump the streamlines lengths to a text file") + Argument ("path").type_file_out() + + Option ("ignorezero", "do not generate a warning if the track file contains streamlines with zero length") + + Tractography::TrackWeightsInOption; } @@ -145,7 +159,7 @@ void run () } } - if (histogram.front()) + if (histogram.front() && !get_options ("ignorezero").size()) WARN ("read " + str(histogram.front()) + " zero-length tracks"); if (count != header_count) WARN ("expected " + str(header_count) + " tracks according to header; read " + str(count)); @@ -169,23 +183,44 @@ void run () stdev += i->get_weight() * Math::pow2 (i->get_length() - mean_length); stdev = std::sqrt (stdev / (((count - 1) / float(count)) * sum_weights)); - const size_t width = 12; + std::vector fields; + auto opt = get_options ("output"); + for (size_t n = 0; n < opt.size(); ++n) + fields.push_back (opt[n][0]); + + if (fields.size()) { - std::cout << " " << std::setw(width) << std::right << "mean" - << " " << std::setw(width) << std::right << "median" - << " " << std::setw(width) << std::right << "std. dev." - << " " << std::setw(width) << std::right << "min" - << " " << std::setw(width) << std::right << "max" - << " " << std::setw(width) << std::right << "count\n"; + 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 << " "; + } + std::cout << "\n"; + + } else { + + const size_t width = 12; - 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" + << " " << std::setw(width) << std::right << "median" + << " " << std::setw(width) << std::right << "std. dev." + << " " << std::setw(width) << std::right << "min" + << " " << 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"; + + } - auto 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); if (!std::isfinite (step_size)) diff --git a/scripts/dwigradcheck b/scripts/dwigradcheck new file mode 100755 index 0000000000..a63677a8fe --- /dev/null +++ b/scripts/dwigradcheck @@ -0,0 +1,154 @@ +#!/usr/bin/env python + +# Script for checking the orientation of the diffusion gradient table + +import copy, decimal, numbers, os, sys +import lib.app + +from lib.binaryInPath import binaryInPath +from lib.delFile import delFile +from lib.errorMessage import errorMessage +from lib.getHeaderInfo import getHeaderInfo +from lib.getTrackStat import getTrackStat +from lib.printMessage import printMessage +from lib.runCommand import runCommand +from lib.warnMessage import warnMessage + +lib.app.author = 'Robert E. Smith (robert.smith@florey.edu.au)' +lib.app.initCitations(['Jeurissen2014']) +lib.app.initParser('Check the orientation of the diffusion gradient table') +lib.app.parser.add_argument('input', help='The input DWI series to be checked') +lib.app.parser.add_argument('-mask', help='Provide a brain mask image') +lib.app.parser.add_argument('-number', type=int, default=10000, help='Set the number of tracks to generate for each test') +grad_import = lib.app.parser.add_mutually_exclusive_group() +grad_import.add_argument('-grad', help='Provide a gradient table in MRtrix format') +grad_import.add_argument('-fslgrad', nargs=2, metavar=('bvecs', 'bvals'), help='Provide a gradient table in FSL bvecs/bvals format') +grad_export = lib.app.parser.add_mutually_exclusive_group() +grad_export.add_argument('-export_grad_mrtrix', metavar='grad', help='Export the final gradient table in MRtrix format') +grad_export.add_argument('-export_grad_fsl', nargs=2, metavar=('bvecs', 'bvals'), help='Export the final gradient table in FSL bvecs/bvals format') +lib.app.initialise() + +image_dimensions = [ int(i) for i in getHeaderInfo(lib.app.args.input, 'size').split() ] +if len(image_dimensions) != 4: + errorMessage('Input image must be a 4D image') +num_volumes = image_dimensions[3] + +# Make sure the image data can be memory-mapped +runCommand('mrconvert ' + lib.app.args.input + ' ' + os.path.join(lib.app.tempDir, 'data.mif') + ' -stride 0,0,0,1 -datatype float32') + +if lib.app.args.grad: + os.copy(lib.app.args.grad, os.path.join(lib.app.tempDir, 'grad.b')) +elif lib.app.args.fslgrad: + os.copy(lib.app.args.fslgrad[0], os.path.join(lib.app.tempDir, 'bvecs')) + os.copy(lib.app.args.fslgrad[1], os.path.join(lib.app.tempDir, 'bvals')) +if lib.app.args.mask: + runCommand('mrconvert ' + lib.app.args.mask + ' ' + os.path.join(lib.app.tempDir, 'mask.mif') + ' -datatype bit') + +lib.app.gotoTempDir() + + + +# Make sure we have gradient table stored externally to header in both MRtrix and FSL formats +if not os.path.isfile('grad.b'): + if os.path.isfile('bvecs'): + runCommand('mrinfo data.mif -fslgrad bvecs bvals -export_grad_mrtrix grad.b') + else: + runCommand('mrinfo data.mif -export_grad_mrtrix grad.b') + +if not os.path.isfile('bvecs'): + if os.path.isfile('grad.b'): + runCommand('mrinfo data.mif -grad grad.b -export_grad_fsl bvecs bvals') + else: + runCommand('mrinfo data.mif -export_grad_fsl bvecs bvals') + +# Import both of these into local memory +with open('grad.b', 'r') as f: + grad_mrtrix = f.read().split('\n') +# Erase the empty last line if necessary +if len(grad_mrtrix[-1]) <= 1: + grad_mrtrix.pop() +# Is our gradient table of the correct length? +if not len(grad_mrtrix) == num_volumes: + errorMessage('Number of entries in gradient table does not match number of DWI volumes') +# Turn into a float matrix +grad_mrtrix = [ [ float(f) for f in line.split(',') ] for line in grad_mrtrix ] + +# Generate a brain mask if we weren't provided with one +if not os.path.exists('mask.mif'): + runCommand('dwi2mask data.mif mask.mif') + +# How many tracks are we going to generate? +number_option = ' -number ' + str(lib.app.args.number) + + +# +# TODO What variations of gradient errors can we conceive? +# * Have the gradients been defined with respect to image space rather than scanner space? +# * After conversion to gradients in image space, are they _then_ defined with respect to scanner space? +# * Has an axis been flipped? (none, 0, 1, 2) +# * Have axes been swapped? (012 021 102 120 201 210) +# * For both flips & swaps, it could occur in either scanner or image space... + +# For now, let's focus on the flips & swaps in scanner space; other components can be added later + + + +axis_flips = [ 'none', 0, 1, 2 ] +axis_permutations = [ ( 0, 1, 2 ), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0) ] + + +# Dictionary with multiple keys per value +lengths = {} + +# Mean length when using unmodified gradient table +orig_meanlength = 0.0 + +for flip in axis_flips: + for permutation in axis_permutations: + + grad = copy.copy(grad_mrtrix) + + # Don't do anything if there aren't any axis flips occurring (flip == 'none') + if isinstance(flip, numbers.Number): + multiplier = [ 1.0, 1.0, 1.0, 1.0 ] + multiplier[flip] = -1.0 + grad = [ [ r*m for r,m in zip(row, multiplier) ] for row in grad ] + + grad = [ [ row[permutation[0]], row[permutation[1]], row[permutation[2]], row[3] ] for row in grad ] + + suffix = '_flip' + str(flip) + '_perm' + ''.join(str(item) for item in permutation) + + # Create the gradient table file + grad_path = 'grad' + suffix + '.b' + with open(grad_path, 'w') as f: + for line in grad: + f.write (','.join([str(v) for v in line]) + '\n') + + # Run the tracking experiment + runCommand('tckgen -algorithm tensor_det data.mif -grad ' + grad_path + ' -seed_image mask.mif -mask mask.mif' + number_option + ' -minlength 0 -downsample 5 tracks' + suffix + '.tck') + + # Get the mean track length + # Upgrage tckstats: Need access to individual parameters, like what is provided by mrstats + meanlength = getTrackStat('tracks' + suffix + '.tck', 'mean') + + # Add to the database + lengths[(flip,permutation)] = meanlength + + # If this is the unmodified gradient table, want to save it + if flip == 'none' and permutation == [ 0, 1, 2 ]: + orig_meanlength = meanlength + + + +print ('Axis flipped Axis permutations Mean length') +for line in sorted(lengths, key=lengths.get): + if isinstance(line[0], numbers.Number): + flip_str = "{:4d}".format(line[0]) + else: + flip_str = line[0] + print (flip_str + ' ' + str(line[1]) + ' ' + "{:5.2f}".format(lengths[line])) + + + +lib.app.complete() + diff --git a/scripts/lib/app.py b/scripts/lib/app.py index a2b04d1587..74acdf4195 100644 --- a/scripts/lib/app.py +++ b/scripts/lib/app.py @@ -9,7 +9,7 @@ cleanup = True lastFile = '' mrtrixForce = '' -mrtrixQuiet = '-quiet' +mrtrixQuiet = ' -quiet' mrtrixNThreads = '' parser = '' refList = '' @@ -119,10 +119,10 @@ def initialise(): if args.nocleanup: cleanup = False if args.nthreads: - mrtrixNThreads = '-nthreads ' + args.nthreads + mrtrixNThreads = ' -nthreads ' + args.nthreads if args.quiet: verbosity = 0 - mrtrixQuiet = '-quiet' + mrtrixQuiet = ' -quiet' if args.verbose: verbosity = 2 mrtrixQuiet = '' @@ -200,25 +200,29 @@ def complete(): sys.stdout.write(os.path.basename(sys.argv[0]) + ': ' + colourPrint + 'Contents of temporary directory kept, location: ' + tempDir + colourClear + '\n') sys.stdout.flush() + + def make_dir(dir): - import os - if not os.path.exists(dir): - os.makedirs(dir) + import os + if not os.path.exists(dir): + os.makedirs(dir) + # determines the common postfix for a list of filenames (including the file extension) def getCommonPostfix(inputFiles): - first = inputFiles[0]; - cursor = 0 - found = False; - common = '' - for i in reversed(first): - if found == False: - for j in inputFiles: - if j[len(j)-cursor-1] != first[len(first)-cursor-1]: - found = True - break - if found == False: - common = first[len(first)-cursor-1] + common - cursor += 1 - return common + first = inputFiles[0]; + cursor = 0 + found = False; + common = '' + for i in reversed(first): + if found == False: + for j in inputFiles: + if j[len(j)-cursor-1] != first[len(first)-cursor-1]: + found = True + break + if found == False: + common = first[len(first)-cursor-1] + common + cursor += 1 + return common + diff --git a/scripts/lib/citations.py b/scripts/lib/citations.py index 9656983cc0..835d22c6e5 100644 --- a/scripts/lib/citations.py +++ b/scripts/lib/citations.py @@ -8,6 +8,7 @@ ( 'fast', True, 'Zhang, Y.; Brady, M. & Smith, S. Segmentation of brain MR images through a hidden Markov random field model and the expectation-maximization algorithm. IEEE Transactions on Medical Imaging, 2001, 20, 45-57'), ( 'first', True, 'Patenaude, B.; Smith, S. M.; Kennedy, D. N. & Jenkinson, M. A Bayesian model of shape and appearance for subcortical brain segmentation. NeuroImage, 2011, 56, 907-922'), ( 'FSL', True, 'Smith, S. M.; Jenkinson, M.; Woolrich, M. W.; Beckmann, C. F.; Behrens, T. E.; Johansen-Berg, H.; Bannister, P. R.; De Luca, M.; Drobnjak, I.; Flitney, D. E.; Niazy, R. K.; Saunders, J.; Vickers, J.; Zhang, Y.; De Stefano, N.; Brady, J. M. & Matthews, P. M. Advances in functional and structural MR image analysis and implementation as FSL. NeuroImage, 2004, 23, S208-S219'), + ( 'Jeurissen2014', False, 'Jeurissen, B.; Leemans, A. & Sijbers, J. Automated correction of improperly rotated diffusion gradient orientations in diffusion weighted MRI. Medical Image Analysis, 2014, 18, 953-962'), ( 'MSMT_CSD', False, 'Jeurissen, B.; Tournier, J.-D.; Dhollander, T.; Connelly, A. & Sijbers, J. Multi-tissue constrained spherical deconvolution for improved analysis of multi-shell diffusion MRI data. NeuroImage, 2014, 103, 411-426'), ( 'N4', True, 'Tustison, N.; Avants, B.; Cook, P.; Zheng, Y.; Egan, A.; Yushkevich, P. & Gee, J. N4ITK: Improved N3 Bias Correction. IEEE Transactions on Medical Imaging, 2010, 29, 1310-1320'), ( 'SD', False, 'Tournier, J.-D.; Calamante, F.; Gadian, D. G. & Connelly, A. Direct estimation of the fiber orientation density function from diffusion-weighted MRI data using spherical deconvolution. NeuroImage, 2004, 23, 1176-1185'), diff --git a/scripts/lib/getTrackStat.py b/scripts/lib/getTrackStat.py new file mode 100644 index 0000000000..81805e7b7e --- /dev/null +++ b/scripts/lib/getTrackStat.py @@ -0,0 +1,13 @@ +def getTrackStat(track_path, statistic): + import lib.app, os, subprocess, sys + from lib.printMessage import printMessage + command = 'tckstats ' + track_path + ' -output ' + statistic + ' -ignorezero' + lib.app.mrtrixQuiet + if lib.app.verbosity > 1: + printMessage('Command: \'' + command + '\' (piping data to local storage)') + proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=None, shell=True) + result = proc.stdout.read() + result = result.rstrip().decode('utf-8') + if lib.app.verbosity > 1: + printMessage('Result: ' + result) + return float(result) + diff --git a/scripts/lib/runCommand.py b/scripts/lib/runCommand.py index e6047b0886..097eed7835 100644 --- a/scripts/lib/runCommand.py +++ b/scripts/lib/runCommand.py @@ -35,11 +35,11 @@ def runCommand(cmd, exitOnError=True): for index, item in enumerate(cmdsplit): if item == '|': if lib.app.mrtrixNThreads: - cmdsplit[index] = lib.app.mrtrixNThreads + ' |' + cmdsplit[index] = lib.app.mrtrixNThreads.strip() + ' |' if lib.app.mrtrixQuiet: - cmdsplit[index] = lib.app.mrtrixQuiet + ' ' + cmdsplit[index] - cmdsplit.append(lib.app.mrtrixNThreads) - cmdsplit.append(lib.app.mrtrixQuiet) + cmdsplit[index] = lib.app.mrtrixQuiet.strip() + ' ' + cmdsplit[index] + cmdsplit.append(lib.app.mrtrixNThreads.strip()) + cmdsplit.append(lib.app.mrtrixQuiet.strip()) cmd = ' '.join(cmdsplit) if lib.app.verbosity: From f9049747a550a793ad3590b5d97e882947ad82dd Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 19 May 2016 20:42:57 +1000 Subject: [PATCH 002/723] New command: amp2response This command estimates a single-fibre response function based on a mask of single-fibre voxels and an estimated fibre direction in each voxel. Unlike the sh2response command, which simply aligns the per-voxel signals and averages their m=0 SH coefficients, this method performs a least-squares fit to the diffusion data in all single-fibre voxels, while enforcing non-negativity and monotonicity constraints. This commit also includes a new header file, lib/math/ZSH.h, which includes functions for dealing with Zonal Spherical Harmonics (spherical harmonics where only the m=0 terms are non-zero). --- cmd/amp2response.cpp | 267 +++++++++++++++++++++++++++++++++++++ lib/math/ZSH.h | 306 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 573 insertions(+) create mode 100644 cmd/amp2response.cpp create mode 100644 lib/math/ZSH.h diff --git a/cmd/amp2response.cpp b/cmd/amp2response.cpp new file mode 100644 index 0000000000..1bd37593c7 --- /dev/null +++ b/cmd/amp2response.cpp @@ -0,0 +1,267 @@ +/* + Copyright 2008 Brain Research Institute, Melbourne, Australia + + Written by J-Donald Tournier 2014 + + This file is part of MRtrix. + + MRtrix is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + MRtrix is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with MRtrix. If not, see . + +*/ + +#include + +#include "command.h" +#include "header.h" +#include "image.h" +#include "image_helpers.h" +#include "types.h" + +#include "math/constrained_least_squares.h" +#include "math/rng.h" +#include "math/ZSH.h" + +#include "dwi/gradient.h" +#include "dwi/shells.h" + + + +using namespace MR; +using namespace App; + + + +#define AMP2RESPONSE_DEBUG + + + +void usage () +{ + + AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au"; + + DESCRIPTION + + "test suite for new mechanisms for estimating spherical deconvolution response functions"; + + 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(); + + OPTIONS + + Option ("directions", "provide an external text file containing the directions along which the amplitudes are sampled") + + Argument("path").type_file_in() + + + DWI::ShellOption + + + Option ("lmax", "specify the maximum harmonic degree of the response function to estimate") + + Argument ("value").type_integer (0, 20); +} + + + +Eigen::Matrix gen_rotation_matrix (const Eigen::Vector3& 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::Vector3 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::Vector3 vec3 = dir.cross (vec2); + vec3.normalize(); + R (1, 0) = vec3[0]; R (1, 1) = vec3[1]; R (1, 2) = vec3[2]; + return R; +} + + +std::vector all_volumes (const size_t num) +{ + std::vector result; + result.reserve (num); + for (size_t i = 0; i != num; ++i) + result.push_back (i); + return result; +} + + +void run () +{ + + // Get directions from either selecting a b-value shell, or the header, or external file + auto header = Header::open (argument[0]); + + Eigen::MatrixXd dirs_azel; + std::vector volumes; + + auto opt = get_options ("directions"); + if (opt.size()) { + dirs_azel = load_matrix (opt[0][0]); + volumes = all_volumes (dirs_azel.rows()); + // TODO Switch between az/el pairs and XYZ triplets + } else { + auto hit = header.keyval().find ("directions"); + if (hit != header.keyval().end()) { + std::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()); + } + dirs_azel.resize (dir_vector.size() / 2, 2); + for (size_t i = 0; i < dir_vector.size(); i += 2) { + dirs_azel (i/2, 0) = dir_vector[i]; + dirs_azel (i/2, 1) = dir_vector[i+1]; + } + volumes = all_volumes (dirs_azel.rows()); + } else { + auto grad = DWI::get_valid_DW_scheme (header); + DWI::Shells shells (grad); + shells.select_shells (false, true); + volumes = shells.largest().get_volumes(); + dirs_azel = DWI::gen_direction_matrix (grad, volumes); + } + } + + // TODO These functions should move + Eigen::MatrixXd dirs_cartesian = Math::SH::spherical2cartesian (dirs_azel); + + const size_t lmax = get_option_value ("lmax", Math::SH::LforN (volumes.size())); + + auto image = header.get_image(); + auto mask = Image::open (argument[1]); + check_dimensions (image, mask, 0, 3); + 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); + + // All directions from all SF voxels get concatenated into a single large matrix + Eigen::MatrixXd cat_transforms; + Eigen::VectorXd cat_data; + +#ifdef AMP2RESPONSE_DEBUG + // To make sure we've got our data rotated correctly, let's generate a scatterplot of + // elevation vs. amplitude + Eigen::MatrixXd scatter; +#endif + + size_t sf_counter = 0; + for (auto l = Loop (mask) (image, mask, dir_image); l; ++l) { + if (mask.value()) { + + // Grab the image data + Eigen::VectorXd data (dirs_azel.rows()); + if (volumes.size()) { + for (size_t i = 0; i != volumes.size(); ++i) { + image.index(3) = volumes[i]; + data[i] = image.value(); + } + } else { + for (image.index(3) = 0; image.index(3) != image.size(3); ++image.index(3)) + data[image.index(3)] = image.value(); + } + + // Grab the fibre direction + // TODO Eventually, it might be possible to optimise these fibre directions + // during the response function fit; i.e. optimise (az,el) in each voxel + // to minimise SSE compared to the current RF estimate + Eigen::Vector3 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 + Eigen::Matrix R = gen_rotation_matrix (fibre_dir); + Eigen::Matrix rotated_dirs_cartesian (dirs_cartesian.rows(), 3); + Eigen::Vector3 vec (3), rot (3); + for (size_t row = 0; row != size_t(dirs_azel.rows()); ++row) { + vec = dirs_cartesian.row (row); + rot = R * vec; + rotated_dirs_cartesian.row (row) = rot; + } + + // Convert directions from Euclidean space to azimuth/elevation pairs + Eigen::MatrixXd rotated_dirs_azel = Math::SH::cartesian2spherical (rotated_dirs_cartesian); + + // Constrain elevations to between 0 and pi/2 + for (size_t i = 0; i != size_t(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); + } + } + + // Generate the ZSH -> amplitude transform + Eigen::MatrixXd transform = Math::ZSH::init_amp_transform (rotated_dirs_azel.col(1), lmax); + + // Concatenate these data to the ICLS matrices + const size_t old_rows = cat_transforms.rows(); + cat_transforms.conservativeResize (old_rows + transform.rows(), transform.cols()); + cat_transforms.block (old_rows, 0, transform.rows(), transform.cols()) = transform; + cat_data.conservativeResize (old_rows + data.size()); + cat_data.tail (data.size()) = data; + +#ifdef AMP2RESPONSE_DEBUG + scatter.conservativeResize (cat_data.size(), 2); + scatter.block (old_rows, 0, data.size(), 1) = rotated_dirs_azel.col(1); + scatter.block (old_rows, 1, data.size(), 1) = data; +#endif + + ++sf_counter; + + } + } + +#ifdef AMP2RESPONSE_DEBUG + save_matrix (scatter, "scatter.csv"); +#endif + + // Generate the constraint matrix + // We are going to both constrain the amplitudes to be positive, and constrain the derivatives to be positive + const size_t num_angles_constraint = 90; + Eigen::VectorXd els; + els.resize (num_angles_constraint+1); + for (size_t i = 0; i <= num_angles_constraint; ++i) + els[i] = default_type(i) * Math::pi / 180.0; + Eigen::MatrixXd amp_transform = Math::ZSH::init_amp_transform (els, lmax); + Eigen::MatrixXd deriv_transform = Math::ZSH::init_deriv_transform (els, lmax); + + Eigen::MatrixXd constraints; + constraints.resize (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 (cat_transforms, constraints, 1e-10, 1e-10); + auto solver = Math::ICLS::Solver (problem); + + // Estimate the solution + Eigen::VectorXd rf; + const size_t niter = solver (rf, cat_data); + + CONSOLE ("Response function [" + str(rf.transpose()) + " ] solved after " + str(niter) + " iterations from " + str(sf_counter) + " voxels"); + + save_vector (rf, argument[3]); + +} diff --git a/lib/math/ZSH.h b/lib/math/ZSH.h new file mode 100644 index 0000000000..a6810741c4 --- /dev/null +++ b/lib/math/ZSH.h @@ -0,0 +1,306 @@ +/* + Copyright 2008 Brain Research Institute, Melbourne, Australia + + Written by JRobert E. Smith, 26/10/2015. + + This file is part of MRtrix. + + MRtrix is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + MRtrix is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with MRtrix. If not, see . + +*/ + +#ifndef __math_ZSH_h__ +#define __math_ZSH_h__ + +#include + +#include "math/legendre.h" +#include "math/least_squares.h" +#include "math/SH.h" + +namespace MR +{ + namespace Math + { + namespace ZSH + { + + /** \defgroup zonal_spherical_harmonics Zonal Spherical Harmonics + * \brief Classes & functions to manage zonal spherical harmonics + * (spherical harmonic functions containing only m=0 terms). */ + + /** \addtogroup zonal_spherical_harmonics + * @{ */ + + + //! the number of (even-degree) coefficients for the given value of \a lmax + inline size_t NforL (int lmax) + { + return (1 + lmax/2); + } + + //! compute the index for coefficient l + inline size_t index (int l) + { + return (l/2); + } + + //! returns the largest \e lmax given \a N parameters + inline size_t LforN (int N) + { + return (2 * (N-1)); + } + + + + //! form the ZSH->amplitudes matrix for a set of elevation angles + /*! This computes the matrix \a ZSHT mapping zonal spherical harmonic + * coefficients up to maximum harmonic degree \a lmax onto amplitudes on + * a set of elevations stored in a vector */ + template + Eigen::Matrix init_amp_transform (const VectorType& els, const size_t lmax) + { + Eigen::Matrix ZSHT; + ZSHT.resize (els.size(), ZSH::NforL (lmax)); + Eigen::Matrix AL (lmax+1); + for (size_t i = 0; i != size_t(els.size()); i++) { + Legendre::Plm_sph (AL, lmax, 0, std::cos (els[i])); + for (size_t l = 0; l <= lmax; l += 2) + ZSHT (i,index(l)) = AL[l]; + } + return ZSHT; + } + + + + //! form the ZSH->derivatives matrix for a set of elevation angles + /*! This computes the matrix \a ZSHT mapping zonal spherical harmonic + * coefficients up to maximum harmonic degree \a lmax onto derivatives + * with respect to elevation angle, for a set of elevations stored in + * a vector */ + template + Eigen::Matrix init_deriv_transform (const VectorType& els, const size_t lmax) + { + Eigen::Matrix dZSHdelT; + dZSHdelT.resize (els.size(), ZSH::NforL (lmax)); + Eigen::Matrix AL (lmax+1); + for (size_t i = 0; i != size_t(els.size()); i++) { + Legendre::Plm_sph (AL, lmax, 1, std::cos (els[i])); + dZSHdelT (i,index(0)) = 0.0; + for (size_t l = 2; l <= lmax; l += 2) + dZSHdelT (i,index(l)) = AL[l] * sqrt (value_type (l*(l+1))); + } + return dZSHdelT; + } + + + + + template + class Transform { + public: + typedef Eigen::Matrix matrix_type; + + template + Transform (const MatrixType& dirs, const size_t lmax) : + ZSHT (init_amp_transform (dirs.col(1), lmax)), // Elevation angles are second column of aximuth/elevation matrix + iZSHT (pinv (ZSHT)) { } + + template + void A2ZSH (VectorType1& zsh, const VectorType2& amplitudes) const { + zsh.noalias() = iZSHT * amplitudes; + } + template + void ZSH2A (VectorType1& amplitudes, const VectorType2& zsh) const { + amplitudes.noalias() = ZSHT * zsh; + } + + size_t n_ZSH () const { + return ZSHT.cols(); + } + size_t n_amp () const { + return ZSHT.rows(); + } + + const matrix_type& mat_A2ZSH () const { + return iZSHT; + } + const matrix_type& mat_ZSH2A () const { + return ZSHT; + } + + protected: + matrix_type ZSHT, iZSHT; + }; + + + + template + inline typename VectorType::Scalar value ( + const VectorType& coefs, + typename VectorType::Scalar elevation, + const size_t lmax) + { + typedef typename VectorType::Scalar value_type; + Eigen::Matrix AL (lmax+1); + Legendre::Plm_sph (AL, lmax, 0, std::cos(elevation)); + value_type amplitude = 0.0; + for (size_t l = 0; l <= lmax; l += 2) + amplitude += AL[l] * coefs[index(l)]; + return amplitude; + } + + + + template + inline typename VectorType::Scalar derivative ( + const VectorType& coefs, + const typename VectorType::Scalar elevation, + const size_t lmax) + { + typedef typename VectorType::Scalar value_type; + Eigen::Matrix AL (lmax+1); + Legendre::Plm_sph (AL, lmax, 1, std::cos (elevation)); + value_type dZSH_del = 0.0; + for (size_t l = 2; l <= lmax; l += 2) + dZSH_del += AL[l] * coefs[index(l)] * sqrt (value_type (l*(l+1))); + return dZSH_del; + } + + + + template + inline VectorType1& ZSH2SH (VectorType1& sh, const VectorType2& zsh) + { + const size_t lmax = LforN (zsh.size()); + sh.resize (Math::SH::NforL (lmax)); + for (size_t i = 0; i != size_t(sh.size()); ++i) + sh[i] = 0.0; + for (size_t l = 0; l <= lmax; l+=2) + sh[Math::SH::index(l,0)] = zsh[index(l)]; + return sh; + } + + template + inline Eigen::Matrix ZSH2SH (const VectorType& zsh) + { + Eigen::Matrix sh; + ZSH2SH (sh, zsh); + return sh; + } + + + + + template + inline VectorType1& SH2ZSH (VectorType1& zsh, const VectorType2& sh) + { + const size_t lmax = Math::SH::LforN (sh.size()); + zsh.resize (NforL (lmax)); + for (size_t l = 0; l <= lmax; l+=2) + zsh[index(l)] = sh[Math::SH::index(l,0)]; + return zsh; + } + + template + inline Eigen::Matrix SH2ZSH (const VectorType& sh) + { + Eigen::Matrix zsh; + SH2ZSH (zsh, sh); + return zsh; + } + + + + template + inline VectorType1& ZSH2RH (VectorType1& rh, const VectorType2& zsh) + { + typedef typename VectorType2::Scalar value_type; + rh.resize (zsh.size()); + const size_t lmax = LforN (zsh.size()); + Eigen::Matrix AL (lmax+1); + Legendre::Plm_sph (AL, lmax, 0, 1.0); + for (size_t l = 0; l <= lmax; ++l) + rh[index(l)] = zsh[index(l)] / AL[l]; + return rh; + } + + template + inline Eigen::Matrix ZSH2RH (const VectorType& zsh) + { + Eigen::Matrix rh (zsh.size()); + ZSH2RH (rh, zsh); + return rh; + } + + + + //! perform zonal spherical convolution, in place + /*! perform zonal spherical convolution of ZSH coefficients \a zsh with response + * function \a RH, storing the results in place in vector \a zsh. */ + template + inline VectorType1& zsconv (VectorType1& zsh, const VectorType2& RH) + { + assert (zsh.size() >= RH.size()); + for (size_t i = 0; i != size_t(RH.size()); ++i) + zsh[i] *= RH[i]; + return zsh; + } + + + //! perform zonal spherical convolution + /*! perform zonal spherical convolution of SH coefficients \a sh with response + * function \a RH, storing the results in vector \a C. */ + template + inline VectorType1& zsconv (VectorType1& C, const VectorType2& RH, const VectorType3& zsh) + { + assert (zsh.size() >= RH.size()); + C.resize (RH.size()); + for (size_t i = 0; i != size_t(RH.size()); ++i) + C[i] = zsh[i] * RH[i]; + return C; + } + + + + + //! compute ZSH coefficients corresponding to specified tensor + template + inline VectorType& FA2ZSH (VectorType& zsh, default_type FA, default_type ADC, default_type bvalue, const size_t lmax, const size_t precision = 100) + { + default_type a = FA/sqrt (3.0 - 2.0*FA*FA); + default_type ev1 = ADC* (1.0+2.0*a), ev2 = ADC* (1.0-a); + + Eigen::VectorXd sigs (precision); + Eigen::MatrixXd ZSHT (precision, lmax/2+1); + Eigen::Matrix AL; + + for (size_t i = 0; i < precision; i++) { + default_type el = i*Math::pi / (2.0*(precision-1)); + sigs[i] = exp (-bvalue * (ev1*std::cos (el)*std::cos (el) + ev2*std::sin (el)*std::sin (el))); + Legendre::Plm_sph (AL, lmax, 0, std::cos (el)); + for (size_t l = 0; l <= lmax; l+=2) + ZSHT (i,index(l)) = AL[l]; + } + + return (zsh = pinv(ZSHT) * sigs); + } + + + + } + } +} + +#endif From 7227c4737d3ccd1e92a293ce2fa16c577dee4ce6 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 7 Jun 2016 16:59:15 +1000 Subject: [PATCH 003/723] dwigradcheck: Further development - Check for axis flips and permutations in both scanner and image space Provide output gradient table when requested --- docs/getting_started/commands/tckstats.rst | 6 +- docs/getting_started/scripts/dwigradcheck.rst | 74 +++++++++ docs/getting_started/scripts_list.rst | 4 + scripts/dwigradcheck | 151 ++++++++++++------ 4 files changed, 181 insertions(+), 54 deletions(-) create mode 100644 docs/getting_started/scripts/dwigradcheck.rst diff --git a/docs/getting_started/commands/tckstats.rst b/docs/getting_started/commands/tckstats.rst index 0689d09c55..f2e914a561 100644 --- a/docs/getting_started/commands/tckstats.rst +++ b/docs/getting_started/commands/tckstats.rst @@ -20,10 +20,14 @@ calculate statistics on streamlines length. Options ------- +- **-output field** output only the field specified. Multiple such options can be supplied if required. Choices are: mean, median, std, min, max, count. Useful for use in scripts. + - **-histogram path** output a histogram of streamline lengths - **-dump path** dump the streamlines lengths to a text file +- **-ignorezero** do not generate a warning if the track file contains streamlines with zero length + - **-tck_weights_in path** specify a text scalar file containing the streamline weights Standard options @@ -49,7 +53,7 @@ Standard options -**Author:** Robert E. Smith (r.smith@brain.org.au) +**Author:** Robert E. Smith (robert.smith@florey.edu.au) **Copyright:** Copyright (c) 2008-2016 the MRtrix3 contributors diff --git a/docs/getting_started/scripts/dwigradcheck.rst b/docs/getting_started/scripts/dwigradcheck.rst new file mode 100644 index 0000000000..2d2ff66ac7 --- /dev/null +++ b/docs/getting_started/scripts/dwigradcheck.rst @@ -0,0 +1,74 @@ +dwigradcheck +=========== + +Synopsis +-------- + + dwigradcheck [ options ] input + +- *input*: The input DWI series to be checked + +Description +----------- + +Check the orientation of the diffusion gradient table + +Options +------- + +- **-mask** Provide a brain mask image + +- **-number** Set the number of tracks to generate for each test + +- **-grad** Provide a gradient table in MRtrix format + +- **-fslgrad bvecs bvals** Provide a gradient table in FSL bvecs/bvals format + +- **-export_grad_mrtrix grad** Export the final gradient table in MRtrix format + +- **-export_grad_fsl bvecs bvals** Export the final gradient table in FSL bvecs/bvals format + +Standard options +^^^^^^^^^^^^^^^^ + + +- **-continue ** Continue the script from a previous execution; must provide the temporary directory path, and the name of the last successfully-generated file + +- **-force** Force overwrite of output files if pre-existing + +- **-help** Display help information for the script + +- **-nocleanup** Do not delete temporary directory at script completion + +- **-nthreads number** Use this number of threads in MRtrix multi-threaded applications (0 disables multi-threading) + +- **-tempdir /path/to/tmp/** Manually specify the path in which to generate the temporary directory + +- **-quiet** Suppress all console output during script execution + +- **-verbose** Display additional information for every command invoked + +References +^^^^^^^^^^ + +Jeurissen2014: +Jeurissen, B.; Leemans, A. & Sijbers, J. Automated correction of improperly rotated diffusion gradient orientations in diffusion weighted MRI. Medical Image Analysis, 2014, 18, 953-962 + + + +--- + +**Author:** Robert E. Smith (robert.smith@florey.edu.au) + +**Copyright:** +Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org diff --git a/docs/getting_started/scripts_list.rst b/docs/getting_started/scripts_list.rst index 5d4fc438e2..df109d448d 100644 --- a/docs/getting_started/scripts_list.rst +++ b/docs/getting_started/scripts_list.rst @@ -15,6 +15,10 @@ Scripts for external libraries ....... +.. include:: scripts/dwigradcheck.rst +....... + + .. include:: scripts/dwiintensitynorm.rst ....... diff --git a/scripts/dwigradcheck b/scripts/dwigradcheck index a63677a8fe..d068a7639e 100755 --- a/scripts/dwigradcheck +++ b/scripts/dwigradcheck @@ -64,14 +64,22 @@ if not os.path.isfile('bvecs'): # Import both of these into local memory with open('grad.b', 'r') as f: grad_mrtrix = f.read().split('\n') +with open('bvecs', 'r') as f: + grad_fsl = f.read().split('\n') # Erase the empty last line if necessary if len(grad_mrtrix[-1]) <= 1: grad_mrtrix.pop() +if len(grad_fsl[-1]) <= 1: + grad_fsl.pop() +# Turn into float matrices +grad_mrtrix = [ [ float(f) for f in line.split(',') ] for line in grad_mrtrix ] +grad_fsl = [ [ float(f) for f in line.split() ] for line in grad_fsl ] # Is our gradient table of the correct length? if not len(grad_mrtrix) == num_volumes: errorMessage('Number of entries in gradient table does not match number of DWI volumes') -# Turn into a float matrix -grad_mrtrix = [ [ float(f) for f in line.split(',') ] for line in grad_mrtrix ] +if not len(grad_fsl) == 3 or not len(grad_fsl[0]) == num_volumes: + errorMessage('Internal error (inconsistent gradient table storage)') + # Generate a brain mask if we weren't provided with one if not os.path.exists('mask.mif'): @@ -83,71 +91,108 @@ number_option = ' -number ' + str(lib.app.args.number) # # TODO What variations of gradient errors can we conceive? -# * Have the gradients been defined with respect to image space rather than scanner space? -# * After conversion to gradients in image space, are they _then_ defined with respect to scanner space? + +# Done: # * Has an axis been flipped? (none, 0, 1, 2) # * Have axes been swapped? (012 021 102 120 201 210) # * For both flips & swaps, it could occur in either scanner or image space... -# For now, let's focus on the flips & swaps in scanner space; other components can be added later - +# To do: +# * Have the gradients been defined with respect to image space rather than scanner space? +# * After conversion to gradients in image space, are they _then_ defined with respect to scanner space? +# (should the above two be tested independently from the axis flips / permutations?) axis_flips = [ 'none', 0, 1, 2 ] axis_permutations = [ ( 0, 1, 2 ), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0) ] +grad_basis = [ 'scanner', 'image' ] -# Dictionary with multiple keys per value -lengths = {} - -# Mean length when using unmodified gradient table -orig_meanlength = 0.0 +# List where the first element is the mean length +lengths = [ ] for flip in axis_flips: for permutation in axis_permutations: - - grad = copy.copy(grad_mrtrix) - - # Don't do anything if there aren't any axis flips occurring (flip == 'none') - if isinstance(flip, numbers.Number): - multiplier = [ 1.0, 1.0, 1.0, 1.0 ] - multiplier[flip] = -1.0 - grad = [ [ r*m for r,m in zip(row, multiplier) ] for row in grad ] - - grad = [ [ row[permutation[0]], row[permutation[1]], row[permutation[2]], row[3] ] for row in grad ] - - suffix = '_flip' + str(flip) + '_perm' + ''.join(str(item) for item in permutation) - - # Create the gradient table file - grad_path = 'grad' + suffix + '.b' - with open(grad_path, 'w') as f: - for line in grad: - f.write (','.join([str(v) for v in line]) + '\n') - - # Run the tracking experiment - runCommand('tckgen -algorithm tensor_det data.mif -grad ' + grad_path + ' -seed_image mask.mif -mask mask.mif' + number_option + ' -minlength 0 -downsample 5 tracks' + suffix + '.tck') - - # Get the mean track length - # Upgrage tckstats: Need access to individual parameters, like what is provided by mrstats - meanlength = getTrackStat('tracks' + suffix + '.tck', 'mean') - - # Add to the database - lengths[(flip,permutation)] = meanlength - - # If this is the unmodified gradient table, want to save it - if flip == 'none' and permutation == [ 0, 1, 2 ]: - orig_meanlength = meanlength - - - -print ('Axis flipped Axis permutations Mean length') -for line in sorted(lengths, key=lengths.get): - if isinstance(line[0], numbers.Number): - flip_str = "{:4d}".format(line[0]) - else: - flip_str = line[0] - print (flip_str + ' ' + str(line[1]) + ' ' + "{:5.2f}".format(lengths[line])) + for basis in grad_basis: + + suffix = '_flip' + str(flip) + '_perm' + ''.join(str(item) for item in permutation) + '_' + basis + + if basis == 'scanner': + + grad = copy.copy(grad_mrtrix) + + # Don't do anything if there aren't any axis flips occurring (flip == 'none') + if isinstance(flip, numbers.Number): + multiplier = [ 1.0, 1.0, 1.0, 1.0 ] + multiplier[flip] = -1.0 + grad = [ [ r*m for r,m in zip(row, multiplier) ] for row in grad ] + grad = [ [ row[permutation[0]], row[permutation[1]], row[permutation[2]], row[3] ] for row in grad ] + + # Create the gradient table file + grad_path = 'grad' + suffix + '.b' + with open(grad_path, 'w') as f: + for line in grad: + f.write (','.join([str(v) for v in line]) + '\n') + + grad_option = ' -grad ' + grad_path + + elif basis == 'image': + + grad = copy.copy(grad_fsl) + + if isinstance(flip, numbers.Number): + grad[flip] = [ -v for v in grad[flip] ] + + grad = [ grad[permutation[0]], grad[permutation[1]], grad[permutation[2]] ] + + grad_path = 'bvecs' + suffix + with open(grad_path, 'w') as f: + for line in grad: + f.write (' '.join([str(v) for v in line]) + '\n') + + grad_option = ' -fslgrad ' + grad_path + ' bvals' + + # Run the tracking experiment + runCommand('tckgen -algorithm tensor_det data.mif' + grad_option + ' -seed_image mask.mif -mask mask.mif' + number_option + ' -minlength 0 -downsample 5 tracks' + suffix + '.tck') + + # Get the mean track length + meanlength = getTrackStat('tracks' + suffix + '.tck', 'mean') + + # Add to the database + lengths.append([meanlength,flip,permutation,basis]) + + +# Sort the list to find the best gradient configuration(s) +lengths.sort() +lengths.reverse() + + +# Provide a printout of the mean streamline length of each gradient table manipulation +print ('Mean length Axis flipped Axis permutations Axis basis') +for line in lengths: + if isinstance(line[1], numbers.Number): + flip_str = "{:4d}".format(line[1]) + else: + flip_str = line[1] + print ("{:5.2f}".format(line[0]) + ' ' + flip_str + ' ' + str(line[2]) + ' ' + line[3]) + + +# If requested, extract what has been detected as the best gradient table, and +# export it in the format requested by the user +grad_export_option = '' +if lib.app.args.export_grad_mrtrix: + grad_export_option = ' -export_grad_mrtrix ' + getUserPath(lib.app.args.export_grad_mrtrix, True) +elif lib.app.args.export_grad_fsl: + grad_export_option = ' -export_grad_fsl ' + getUserPath(lib.app.args.export_grad_fsl[0], True) + ' ' + getUserPath(lib.app.args.export_grad_fsl[1], True) +if grad_export_option: + best = lengths[0] + suffix = '_flip' + str(best[1]) + '_perm' + ''.join(str(item) for item in best[2]) + '_' + best[3] + if best[3] == 'scanner': + grad_import_option = ' -grad grad' + suffix + '.b' + elif best[3] == 'image': + grad_import_option = ' -fslgrad bvecs' + suffix + ' bvals' + runCommand('mrinfo data.mif' + grad_import_option + grad_export_option) lib.app.complete() From 1071f1f5e50237edde7a647567a3efce606163df Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 23 Jun 2016 15:58:26 +1000 Subject: [PATCH 004/723] Stats: Float & vector type changes - Use Eigen::Array rather than std::vector for floating-point data throughout statistics code. - Perform necessary templating in order for NBS-TFCE code (default_type) to live alongside existing stats code (float). - typedef'd std::vector for storing the empirical null distribution, and used the vector size to flag its use rather than a pointer. --- cmd/fixelcfestats.cpp | 57 ++++---- cmd/mrclusterstats.cpp | 44 +++--- lib/filter/connected_components.h | 9 +- lib/math/math.h | 22 +++ lib/math/stats/glm.h | 31 +++-- lib/math/stats/permutation.h | 24 ++-- src/connectome/connectome.cpp | 56 -------- src/connectome/connectome.h | 57 +++++++- src/gui/mrview/tool/connectome/connectome.cpp | 8 +- src/stats/cfe.h | 8 +- src/stats/cluster.h | 7 +- src/stats/permtest.h | 131 +++++++++--------- src/stats/tfce.h | 12 +- 13 files changed, 255 insertions(+), 211 deletions(-) delete mode 100644 src/connectome/connectome.cpp diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index ca708325eb..d9d40c8d34 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -39,6 +39,8 @@ using namespace MR::DWI::Tractography::Mapping; using Sparse::FixelMetric; typedef float value_type; +typedef Eigen::Matrix matrix_type; +typedef Eigen::Array vector_type; #define DEFAULT_PERMUTATIONS 5000 #define DEFAULT_CFE_DH 0.1 @@ -352,19 +354,21 @@ void run() { { ProgressBar progress ("outputting beta coefficients, effect size and standard deviation"); auto temp = Math::Stats::GLM::solve_betas (data, design); - for (ssize_t i = 0; i < contrast.cols(); ++i) + for (ssize_t i = 0; i < contrast.cols(); ++i) { write_fixel_output (output_prefix + "beta" + str(i) + ".msf", temp.row(i), input_header, mask_fixel_image, fixel_index_image); + ++progress; + } temp = Math::Stats::GLM::abs_effect_size (data, design, contrast); - write_fixel_output (output_prefix + "abs_effect.msf", temp.row(0), input_header, mask_fixel_image, fixel_index_image); + write_fixel_output (output_prefix + "abs_effect.msf", temp.row(0), input_header, mask_fixel_image, fixel_index_image); ++progress; temp = Math::Stats::GLM::std_effect_size (data, design, contrast); - write_fixel_output (output_prefix + "std_effect.msf", temp.row(0), input_header, mask_fixel_image, fixel_index_image); + write_fixel_output (output_prefix + "std_effect.msf", temp.row(0), input_header, mask_fixel_image, fixel_index_image); ++progress; temp = Math::Stats::GLM::stdev (data, design); write_fixel_output (output_prefix + "std_dev.msf", temp.row(0), input_header, mask_fixel_image, fixel_index_image); } - Math::Stats::GLMTTest glm_ttest (data, design, contrast); + Math::Stats::GLMTTest glm_ttest (data, design, contrast); Stats::CFE::Enhancer cfe_integrator (connectivity_matrix, cfe_dh, cfe_e, cfe_h); - std::shared_ptr > empirical_cfe_statistic; + Stats::PermTest::empirical_vector_type empirical_cfe_statistic; Header output_header (input_header); output_header.keyval()["num permutations"] = str(num_perms); @@ -378,20 +382,19 @@ void run() { // If performing non-stationarity adjustment we need to pre-compute the empirical CFE statistic if (do_nonstationary_adjustment) { - empirical_cfe_statistic.reset(new std::vector (num_fixels, 0.0)); - Stats::PermTest::precompute_empirical_stat (glm_ttest, cfe_integrator, nperms_nonstationary, *empirical_cfe_statistic); + Stats::PermTest::precompute_empirical_stat (glm_ttest, cfe_integrator, nperms_nonstationary, empirical_cfe_statistic); output_header.keyval()["nonstationary adjustment"] = str(true); - write_fixel_output (output_prefix + "cfe_empirical.msf", *empirical_cfe_statistic, output_header, mask_fixel_image, fixel_index_image); + write_fixel_output (output_prefix + "cfe_empirical.msf", empirical_cfe_statistic, output_header, mask_fixel_image, fixel_index_image); } else { output_header.keyval()["nonstationary adjustment"] = str(false); } // Precompute default statistic and CFE statistic - std::vector cfe_output (num_fixels, 0.0); - std::shared_ptr > cfe_output_neg; - std::vector tvalue_output (num_fixels, 0.0); + vector_type cfe_output (num_fixels); + std::shared_ptr cfe_output_neg; + vector_type tvalue_output (num_fixels); if (compute_negative_contrast) - cfe_output_neg.reset (new std::vector (num_fixels, 0.0)); + cfe_output_neg.reset (new vector_type (num_fixels)); Stats::PermTest::precompute_default_permutation (glm_ttest, cfe_integrator, empirical_cfe_statistic, cfe_output, cfe_output_neg, tvalue_output); @@ -403,14 +406,14 @@ void run() { // Perform permutation testing opt = get_options ("notest"); if (!opt.size()) { - Eigen::Matrix perm_distribution (num_perms); - std::shared_ptr > perm_distribution_neg; - std::vector uncorrected_pvalues (num_fixels, 0.0); - std::shared_ptr > uncorrected_pvalues_neg; + vector_type perm_distribution (num_perms); + std::shared_ptr perm_distribution_neg; + vector_type uncorrected_pvalues (num_fixels); + std::shared_ptr uncorrected_pvalues_neg; if (compute_negative_contrast) { - perm_distribution_neg.reset (new Eigen::Matrix (num_perms)); - uncorrected_pvalues_neg.reset (new std::vector (num_fixels, 0.0)); + perm_distribution_neg.reset (new vector_type (num_perms)); + uncorrected_pvalues_neg.reset (new vector_type (num_fixels)); } Stats::PermTest::run_permutations (glm_ttest, cfe_integrator, num_perms, empirical_cfe_statistic, @@ -419,18 +422,18 @@ void run() { uncorrected_pvalues, uncorrected_pvalues_neg); ProgressBar progress ("outputting final results"); - save_matrix (perm_distribution, output_prefix + "perm_dist.txt"); + save_matrix (perm_distribution, output_prefix + "perm_dist.txt"); ++progress; - std::vector pvalue_output (num_fixels, 0.0); - Math::Stats::statistic2pvalue (perm_distribution, cfe_output, pvalue_output); - write_fixel_output (output_prefix + "fwe_pvalue.msf", pvalue_output, output_header, mask_fixel_image, fixel_index_image); - write_fixel_output (output_prefix + "uncorrected_pvalue.msf", uncorrected_pvalues, output_header, mask_fixel_image, fixel_index_image); + vector_type pvalue_output (num_fixels); + Math::Stats::statistic2pvalue (perm_distribution, cfe_output, pvalue_output); ++progress; + write_fixel_output (output_prefix + "fwe_pvalue.msf", pvalue_output, output_header, mask_fixel_image, fixel_index_image); ++progress; + write_fixel_output (output_prefix + "uncorrected_pvalue.msf", uncorrected_pvalues, output_header, mask_fixel_image, fixel_index_image); ++progress; if (compute_negative_contrast) { - save_matrix (*perm_distribution_neg, output_prefix + "perm_dist_neg.txt"); - std::vector pvalue_output_neg (num_fixels, 0.0); - Math::Stats::statistic2pvalue (*perm_distribution_neg, *cfe_output_neg, pvalue_output_neg); - write_fixel_output (output_prefix + "fwe_pvalue_neg.msf", pvalue_output_neg, output_header, mask_fixel_image, fixel_index_image); + save_matrix (*perm_distribution_neg, output_prefix + "perm_dist_neg.txt"); ++progress; + vector_type pvalue_output_neg (num_fixels); + Math::Stats::statistic2pvalue (*perm_distribution_neg, *cfe_output_neg, pvalue_output_neg); ++progress; + write_fixel_output (output_prefix + "fwe_pvalue_neg.msf", pvalue_output_neg, output_header, mask_fixel_image, fixel_index_image); ++progress; write_fixel_output (output_prefix + "uncorrected_pvalue_neg.msf", *uncorrected_pvalues_neg, output_header, mask_fixel_image, fixel_index_image); } } diff --git a/cmd/mrclusterstats.cpp b/cmd/mrclusterstats.cpp index 20ac413f46..7d18bf600a 100644 --- a/cmd/mrclusterstats.cpp +++ b/cmd/mrclusterstats.cpp @@ -100,6 +100,8 @@ void usage () typedef Stats::TFCE::value_type value_type; +typedef Eigen::Matrix matrix_type; +typedef Eigen::Array vector_type; void run() { @@ -125,12 +127,12 @@ void run() { } // Load design matrix: - Eigen::Matrix design = load_matrix (argument[1]); + const matrix_type design = load_matrix (argument[1]); if (design.rows() != (ssize_t)subjects.size()) throw Exception ("number of input files does not match number of rows in design matrix"); // Load contrast matrix: - Eigen::Matrix contrast = load_matrix (argument[2]); + matrix_type contrast = load_matrix (argument[2]); if (contrast.cols() != design.cols()) throw Exception ("the number of contrasts does not equal the number of columns in the design matrix"); @@ -142,7 +144,7 @@ void run() { std::vector > mask_indices = connector.precompute_adjacency (mask_image); const size_t num_vox = mask_indices.size(); - Eigen::Matrix data (num_vox, subjects.size()); + matrix_type data (num_vox, subjects.size()); { @@ -193,14 +195,14 @@ void run() { Image fwe_pvalue_image_neg; Image uncorrected_pvalue_image_neg; - Eigen::Matrix perm_distribution (num_perms); - std::shared_ptr > perm_distribution_neg; - std::vector default_cluster_output (num_vox, 0.0); - std::shared_ptr > default_cluster_output_neg; - std::vector tvalue_output (num_vox, 0.0); - std::shared_ptr > empirical_tfce_statistic; - std::vector uncorrected_pvalue (num_vox, 0.0); - std::shared_ptr > uncorrected_pvalue_neg; + vector_type perm_distribution (num_perms); + std::shared_ptr perm_distribution_neg; + vector_type default_cluster_output (num_vox); + std::shared_ptr default_cluster_output_neg; + vector_type tvalue_output (num_vox); + Stats::PermTest::empirical_vector_type empirical_tfce_statistic; + vector_type uncorrected_pvalue (num_vox); + std::shared_ptr uncorrected_pvalue_neg; bool compute_negative_contrast = get_options("negative").size() ? true : false; @@ -211,15 +213,15 @@ void run() { else cluster_neg_name.append ("tfce_neg.mif"); cluster_image_neg = Image::create (cluster_neg_name, output_header); - perm_distribution_neg.reset (new Eigen::Matrix (num_perms)); - default_cluster_output_neg.reset (new std::vector (num_vox, 0.0)); + perm_distribution_neg.reset (new vector_type (num_perms)); + default_cluster_output_neg.reset (new vector_type (num_vox)); fwe_pvalue_image_neg = Image::create (prefix + "fwe_pvalue_neg.mif", output_header); - uncorrected_pvalue_neg.reset (new std::vector (num_vox, 0.0)); + uncorrected_pvalue_neg.reset (new vector_type (num_vox)); uncorrected_pvalue_image_neg = Image::create (prefix + "uncorrected_pvalue_neg.mif", output_header); } { // Do permutation testing: - Math::Stats::GLMTTest glm (data, design, contrast); + Math::Stats::GLMTTest glm (data, design, contrast); // Suprathreshold clustering if (std::isfinite (cluster_forming_threshold)) { @@ -239,10 +241,8 @@ void run() { // TFCE } else { Stats::TFCE::Enhancer tfce_integrator (connector, tfce_dh, tfce_E, tfce_H); - if (do_nonstationary_adjustment) { - empirical_tfce_statistic.reset (new std::vector (num_vox, 0.0)); - Stats::PermTest::precompute_empirical_stat (glm, tfce_integrator, nperms_nonstationary, *empirical_tfce_statistic); - } + if (do_nonstationary_adjustment) + Stats::PermTest::precompute_empirical_stat (glm, tfce_integrator, nperms_nonstationary, empirical_tfce_statistic); Stats::PermTest::precompute_default_permutation (glm, tfce_integrator, empirical_tfce_statistic, default_cluster_output, default_cluster_output_neg, tvalue_output); @@ -256,7 +256,7 @@ void run() { save_matrix (perm_distribution, prefix + "perm_dist.txt"); - std::vector pvalue_output (num_vox, 0.0); + vector_type pvalue_output (num_vox); Math::Stats::statistic2pvalue (perm_distribution, default_cluster_output, pvalue_output); { ProgressBar progress ("generating output"); @@ -267,12 +267,13 @@ void run() { cluster_image.value() = default_cluster_output[i]; fwe_pvalue_image.value() = pvalue_output[i]; uncorrected_pvalue_image.value() = uncorrected_pvalue[i]; + ++progress; } } { if (compute_negative_contrast) { save_matrix (*perm_distribution_neg, prefix + "perm_dist_neg.txt"); - std::vector pvalue_output_neg (num_vox, 0.0); + vector_type pvalue_output_neg (num_vox); Math::Stats::statistic2pvalue (*perm_distribution_neg, *default_cluster_output_neg, pvalue_output_neg); ProgressBar progress ("generating negative contrast output"); @@ -282,6 +283,7 @@ void run() { cluster_image_neg.value() = (*default_cluster_output_neg)[i]; fwe_pvalue_image_neg.value() = pvalue_output_neg[i]; uncorrected_pvalue_image_neg.value() = (*uncorrected_pvalue_neg)[i]; + ++progress; } } diff --git a/lib/filter/connected_components.h b/lib/filter/connected_components.h index 2a08c5c78d..b293db0ef2 100644 --- a/lib/filter/connected_components.h +++ b/lib/filter/connected_components.h @@ -80,9 +80,10 @@ namespace MR // Perform connected components on data with the defined threshold. Assumes adjacency is the same as the mask. + template void run (std::vector& clusters, std::vector& labels, - const std::vector& data, + const VectorType& data, const float threshold) const { labels.resize (adjacent_indices.size(), 0); uint32_t current_label = 1; @@ -176,9 +177,10 @@ namespace MR } + template bool next_neighbour (uint32_t& node, std::vector& labels, - const std::vector& data, + const VectorType& data, const float threshold) const { for (size_t n = 0; n < adjacent_indices[node].size(); n++) { if (labels[adjacent_indices[node][n]] == 0 && data[adjacent_indices[node][n]] > threshold) { @@ -214,10 +216,11 @@ namespace MR } // use a non-recursive depth first search to agglomerate adjacent voxels + template void depth_first_search (uint32_t root, cluster& cluster, std::vector& labels, - const std::vector& data, + const VectorType& data, const float threshold) const { uint32_t node = root; std::stack stack; diff --git a/lib/math/math.h b/lib/math/math.h index c0c5789a9e..aea30b1d83 100644 --- a/lib/math/math.h +++ b/lib/math/math.h @@ -103,6 +103,28 @@ namespace MR } /** @} */ + //! convenience functions for SFINAE on std:: / Eigen containers + template + class is_eigen_type { + typedef char yes[1], no[2]; + template static yes& test(typename Cont::Scalar); + template static no& test(...); + public: + static const bool value = sizeof(test(0)) == sizeof(yes); + }; + + //! Get the underlying scalar value type for both std:: containers and Eigen + template + class container_value_type { + public: + typedef typename Cont::value_type type; + }; + template + class container_value_type ::value, int>::type> { + public: + typedef typename Cont::Scalar type; + }; + //! write the matrix \a M to file template diff --git a/lib/math/stats/glm.h b/lib/math/stats/glm.h index 3d6cfee7af..8d6bdc9f80 100644 --- a/lib/math/stats/glm.h +++ b/lib/math/stats/glm.h @@ -26,8 +26,9 @@ namespace MR { namespace Stats { + namespace GLM + { - namespace GLM { //! scale contrasts for use in t-test /*! Note each row of the contrast matrix will be treated as an independent contrast. The number @@ -138,22 +139,24 @@ namespace MR /** \addtogroup Statistics @{ */ /*! A class to compute t-statistics using a General Linear Model. */ + template class GLMTTest { public: + typedef ValueType value_type; /*! * @param measurements a matrix storing the measured data for each subject in a column //TODO * @param design the design matrix (unlike other packages a column of ones is NOT automatically added for correlation analysis) * @param contrast a matrix containing the contrast of interest. */ - GLMTTest (const Eigen::MatrixXf& measurements, - const Eigen::MatrixXf& design, - const Eigen::MatrixXf& contrast) : + GLMTTest (const Eigen::Matrix& measurements, + const Eigen::Matrix& design, + const Eigen::Matrix& contrast) : y (measurements), X (design), scaled_contrasts (GLM::scale_contrasts (contrast, X, X.rows()-rank(X)).transpose()) { - pinvX = Math::pinv (X.cast()).template cast(); + pinvX = Math::pinv (X.template cast()).template cast(); } /*! Compute the t-statistics @@ -162,11 +165,11 @@ namespace MR * @param max_stat the maximum t-statistic * @param min_stat the minimum t-statistic */ - void operator() (const std::vector& perm_labelling, std::vector& stats, - float& max_stat, float& min_stat) const + void operator() (const std::vector& perm_labelling, Eigen::Array& stats, + ValueType& max_stat, ValueType& min_stat) const { - stats.resize (y.rows(), 0.0); - Eigen::MatrixXf tvalues, betas, residuals, SX, pinvSX; + stats = Eigen::Array::Zero (y.rows()); + Eigen::Matrix tvalues, betas, residuals, SX, pinvSX; SX.resize (X.rows(), X.cols()); pinvSX.resize (pinvX.rows(), pinvX.cols()); @@ -178,17 +181,17 @@ namespace MR pinvSX.transposeInPlace(); SX.transposeInPlace(); for (ssize_t i = 0; i < y.rows(); i += GLM_BATCH_SIZE) { - Eigen::MatrixXf tmp = y.block (i, 0, std::min (GLM_BATCH_SIZE, (int)(y.rows()-i)), y.cols()); + const Eigen::Matrix tmp = y.block (i, 0, std::min (GLM_BATCH_SIZE, (int)(y.rows()-i)), y.cols()); GLM::ttest (tvalues, SX, pinvSX, tmp, scaled_contrasts, betas, residuals); for (ssize_t n = 0; n < tvalues.rows(); ++n) { - float val = tvalues(n,0); + ValueType val = tvalues(n,0); if (std::isfinite (val)) { if (val > max_stat) max_stat = val; if (val < min_stat) min_stat = val; } else { - val = float(0.0); + val = ValueType(0); } stats[i+n] = val; } @@ -199,8 +202,8 @@ namespace MR size_t num_elements () const { return y.rows(); } protected: - const Eigen::MatrixXf& y; - Eigen::MatrixXf X, pinvX, scaled_contrasts; + const Eigen::Matrix& y; + Eigen::Matrix X, pinvX, scaled_contrasts; }; //! @} diff --git a/lib/math/stats/permutation.h b/lib/math/stats/permutation.h index 985067591f..9562e9b30d 100644 --- a/lib/math/stats/permutation.h +++ b/lib/math/stats/permutation.h @@ -15,6 +15,8 @@ #ifndef __math_stats_permutation_h__ #define __math_stats_permutation_h__ +#include "math/math.h" + namespace MR { namespace Math @@ -22,7 +24,6 @@ namespace MR namespace Stats { - typedef float value_type; inline bool is_duplicate_vector (const std::vector& v1, const std::vector& v2) @@ -38,7 +39,7 @@ namespace MR inline bool is_duplicate_permutation (const std::vector& perm, const std::vector >& previous_permutations) { - for (unsigned int p = 0; p < previous_permutations.size(); p++) { + for (size_t p = 0; p < previous_permutations.size(); p++) { if (is_duplicate_vector (perm, previous_permutations[p])) return true; } @@ -72,21 +73,23 @@ namespace MR } - inline void statistic2pvalue (const Eigen::Matrix& perm_dist, - const std::vector& stats, - std::vector& pvalues) + template + inline void statistic2pvalue (const VectorType& perm_dist, + const VectorType& stats, + VectorType& pvalues) { - std::vector permutations (perm_dist.size(), 0); + typedef typename container_value_type::type value_type; + std::vector permutations (perm_dist.size(), 0); for (ssize_t i = 0; i < perm_dist.size(); i++) permutations[i] = perm_dist[i]; std::sort (permutations.begin(), permutations.end()); pvalues.resize (stats.size()); - for (size_t i = 0; i < stats.size(); ++i) { + for (size_t i = 0; i < size_t(stats.size()); ++i) { if (stats[i] > 0.0) { value_type pvalue = 1.0; - for (size_t j = 0; j < permutations.size(); ++j) { + for (size_t j = 0; j < size_t(permutations.size()); ++j) { if (stats[i] < permutations[j]) { - pvalue = value_type (j) / value_type (permutations.size()); + pvalue = value_type(j) / value_type(permutations.size()); break; } } @@ -96,6 +99,9 @@ namespace MR pvalues[i] = 0.0; } } + + + } } } diff --git a/src/connectome/connectome.cpp b/src/connectome/connectome.cpp deleted file mode 100644 index 40a90ad16f..0000000000 --- a/src/connectome/connectome.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - - - -#include "connectome/connectome.h" - -#include "exception.h" -#include "mrtrix.h" - - -namespace MR { -namespace Connectome { - - -void verify_matrix (matrix_type& in, const node_t num_nodes) -{ - if (in.rows() != in.cols()) - throw Exception ("Connectome matrix is not square (" + str(in.rows()) + " x " + str(in.cols()) + ")"); - if (in.rows() != num_nodes) - throw Exception ("Connectome matrix contains " + str(in.rows()) + " nodes; expected " + str(num_nodes)); - - for (node_t row = 0; row != num_nodes; ++row) { - for (node_t column = row+1; column != num_nodes; ++column) { - - const float lower_value = in (column, row); - const float upper_value = in (row, column); - - if (upper_value && lower_value && (upper_value != lower_value)) - throw Exception ("Connectome matrix is not symmetrical"); - - if (!upper_value && lower_value) - in (row, column) = lower_value; - - in (column, row) = 0.0f; - - } } -} - - -} -} - - diff --git a/src/connectome/connectome.h b/src/connectome/connectome.h index 5d38758f99..8a76d6fa04 100644 --- a/src/connectome/connectome.h +++ b/src/connectome/connectome.h @@ -19,6 +19,10 @@ #define __connectome_connectome_h__ +#include + +#include "exception.h" +#include "mrtrix.h" #include "types.h" @@ -26,12 +30,61 @@ namespace MR { namespace Connectome { + typedef uint32_t node_t; -typedef Eigen::Matrix matrix_type; +typedef default_type value_type; +typedef Eigen::Array matrix_type; +typedef Eigen::Array vector_type; +typedef Eigen::Array mask_type; + + + +template +void to_upper (MatrixType& in) +{ + if (in.rows() != in.cols()) + throw Exception ("Connectome matrix is not square (" + str(in.rows()) + " x " + str(in.cols()) + ")"); + + for (node_t row = 0; row != in.rows(); ++row) { + for (node_t col = row+1; col != in.cols(); ++col) { + + const typename MatrixType::Scalar lower_value = in (col, row); + const typename MatrixType::Scalar upper_value = in (row, col); + + if (upper_value && lower_value && (upper_value != lower_value)) + throw Exception ("Connectome matrix is not symmetrical"); + + if (!upper_value && lower_value) + in (row, col) = lower_value; + + in (col, row) = typename MatrixType::Scalar(0); + } } +} + + + +template +void check (const MatrixType& in, const node_t num_nodes) +{ + if (in.rows() != in.cols()) + throw Exception ("Connectome matrix is not square (" + str(in.rows()) + " x " + str(in.cols()) + ")"); + if (in.rows() != num_nodes) + throw Exception ("Connectome matrix contains " + str(in.rows()) + " nodes; expected " + str(num_nodes)); + + for (node_t row = 0; row != num_nodes; ++row) { + for (node_t col = row+1; col != num_nodes; ++col) { + + const typename MatrixType::Scalar lower_value = in (col, row); + const typename MatrixType::Scalar upper_value = in (row, col); + + if (upper_value && lower_value && (upper_value != lower_value)) + throw Exception ("Connectome matrix is not symmetrical"); + + } } +} -void verify_matrix (matrix_type&, const node_t); } diff --git a/src/gui/mrview/tool/connectome/connectome.cpp b/src/gui/mrview/tool/connectome/connectome.cpp index 639ffb51da..d5b53afff9 100644 --- a/src/gui/mrview/tool/connectome/connectome.cpp +++ b/src/gui/mrview/tool/connectome/connectome.cpp @@ -2338,7 +2338,9 @@ namespace MR for (size_t i = 0; i < list.size(); ++i) { try { MR::Connectome::matrix_type matrix = MR::load_matrix (list[i]); - MR::Connectome::verify_matrix (matrix, num_nodes()); + MR::Connectome::to_upper (matrix); + if (matrix.rows() != num_nodes()) + throw Exception ("Matrix file \"" + Path::basename(list[i]) + "\" is incorrect size"); FileDataVector temp; mat2vec (matrix, temp); temp.calc_stats(); @@ -2730,7 +2732,9 @@ namespace MR MR::Connectome::matrix_type temp; try { temp = MR::load_matrix (path); - MR::Connectome::verify_matrix (temp, num_nodes()); + MR::Connectome::to_upper (temp); + if (temp.rows() != num_nodes()) + throw Exception ("Matrix file \"" + Path::basename(path) + "\" is incorrect size"); } catch (Exception& e) { e.display(); return false; diff --git a/src/stats/cfe.h b/src/stats/cfe.h index 24e2767de3..d9860ba1d5 100644 --- a/src/stats/cfe.h +++ b/src/stats/cfe.h @@ -27,6 +27,7 @@ namespace MR { typedef float value_type; + typedef Eigen::Array vector_type; typedef DWI::Tractography::Mapping::SetVoxelDir SetVoxelDir; @@ -121,11 +122,10 @@ namespace MR const value_type dh, const value_type E, const value_type H) : connectivity_map (connectivity_map), dh (dh), E (E), H (H) { } - value_type operator() (const value_type max_stat, const std::vector& stats, - std::vector& enhanced_stats) const + value_type operator() (const value_type max_stat, const vector_type& stats, + vector_type& enhanced_stats) const { - enhanced_stats.resize (stats.size()); - std::fill (enhanced_stats.begin(), enhanced_stats.end(), 0.0); + enhanced_stats = vector_type::Zero(stats.size()); value_type max_enhanced_stat = 0.0; for (size_t fixel = 0; fixel < connectivity_map.size(); ++fixel) { std::map::const_iterator connected_fixel; diff --git a/src/stats/cluster.h b/src/stats/cluster.h index a25c6110ee..4f9f87ba9a 100644 --- a/src/stats/cluster.h +++ b/src/stats/cluster.h @@ -26,6 +26,7 @@ namespace MR { typedef float value_type; + typedef Eigen::Array vector_type; /** \addtogroup Statistics @@ -35,14 +36,14 @@ namespace MR ClusterSize (const Filter::Connector& connector, value_type cluster_forming_threshold) : connector (connector), cluster_forming_threshold (cluster_forming_threshold) { } - value_type operator() (const value_type unused, const std::vector& stats, - std::vector& get_cluster_sizes) const + value_type operator() (const value_type unused, const vector_type& stats, + vector_type& get_cluster_sizes) const { std::vector clusters; std::vector labels (stats.size(), 0); connector.run (clusters, labels, stats, cluster_forming_threshold); get_cluster_sizes.resize (stats.size()); - for (size_t i = 0; i < stats.size(); ++i) + for (size_t i = 0; i < size_t(stats.size()); ++i) get_cluster_sizes[i] = labels[i] ? clusters[labels[i]-1].size : 0.0; return clusters.size() ? std::max_element (clusters.begin(), clusters.end())->size : 0.0; diff --git a/src/stats/permtest.h b/src/stats/permtest.h index 867dfd94ce..772611084b 100644 --- a/src/stats/permtest.h +++ b/src/stats/permtest.h @@ -15,10 +15,12 @@ #ifndef __stats_permtest_h__ #define __stats_permtest_h__ +#include #include #include "progressbar.h" #include "thread.h" +#include "math/math.h" #include "math/stats/permutation.h" #include "thread_queue.h" @@ -29,8 +31,8 @@ namespace MR namespace PermTest { - typedef float value_type; + typedef std::vector empirical_vector_type; class PermutationStack { @@ -58,19 +60,19 @@ namespace MR protected: size_t current_permutation; ProgressBar progress; - std::vector > permutations; + std::vector< std::vector > permutations; std::mutex permutation_mutex; }; - /*! A class to pre-compute the empirical TFCE or CFE statistic image for non-stationarity correction */ + /*! A class to pre-compute the empirical enhanced statistic image for non-stationarity correction */ template class PreProcessor { public: PreProcessor (PermutationStack& permutation_stack, const StatsType& stats_calculator, - const EnchancementType& enhancer, std::vector& global_enhanced_sum, + const EnchancementType& enhancer, empirical_vector_type& global_enhanced_sum, std::vector& global_enhanced_count) : perm_stack (permutation_stack), stats_calculator (stats_calculator), enhancer (enhancer), global_enhanced_sum (global_enhanced_sum), @@ -98,10 +100,10 @@ namespace MR void process_permutation (size_t index) { - value_type max_stat = 0.0, min_stat = 0.0; + typename StatsType::value_type max_stat = 0.0, min_stat = 0.0; stats_calculator (perm_stack.permutation (index), stats, max_stat, min_stat); enhancer (max_stat, stats, enhanced_stats); - for (size_t i = 0; i < enhanced_stats.size(); ++i) { + for (size_t i = 0; i < size_t(enhanced_stats.size()); ++i) { if (enhanced_stats[i] > 0.0) { enhanced_sum[i] += enhanced_stats[i]; enhanced_count[i]++; @@ -112,12 +114,12 @@ namespace MR PermutationStack& perm_stack; StatsType stats_calculator; EnchancementType enhancer; - std::vector& global_enhanced_sum; + empirical_vector_type& global_enhanced_sum; std::vector& global_enhanced_count; - std::vector enhanced_sum; + empirical_vector_type enhanced_sum; std::vector enhanced_count; - std::vector stats; - std::vector enhanced_stats; + Eigen::Array stats; + Eigen::Array enhanced_stats; std::shared_ptr mutex; }; @@ -125,14 +127,14 @@ namespace MR /*! A class to perform the permutation testing */ - template + template class Processor { public: Processor (PermutationStack& permutation_stack, const StatsType& stats_calculator, - const EnhancementType& enhancer, const std::shared_ptr >& empirical_enhanced_statistics, - const std::vector& default_enhanced_statistics, const std::shared_ptr >& default_enhanced_statistics_neg, - Eigen::Matrix& perm_dist_pos, std::shared_ptr >& perm_dist_neg, - std::vector& global_uncorrected_pvalue_counter, std::shared_ptr >& global_uncorrected_pvalue_counter_neg) : + const EnhancementType& enhancer, const empirical_vector_type& empirical_enhanced_statistics, + const VectorType& default_enhanced_statistics, const std::shared_ptr default_enhanced_statistics_neg, + VectorType& perm_dist_pos, std::shared_ptr perm_dist_neg, + std::vector& global_uncorrected_pvalue_counter, std::shared_ptr > global_uncorrected_pvalue_counter_neg) : perm_stack (permutation_stack), stats_calculator (stats_calculator), enhancer (enhancer), empirical_enhanced_statistics (empirical_enhanced_statistics), default_enhanced_statistics (default_enhanced_statistics), default_enhanced_statistics_neg (default_enhanced_statistics_neg), @@ -140,9 +142,10 @@ namespace MR uncorrected_pvalue_counter (stats_calculator.num_elements(), 0), perm_dist_pos (perm_dist_pos), perm_dist_neg (perm_dist_neg), global_uncorrected_pvalue_counter (global_uncorrected_pvalue_counter), - global_uncorrected_pvalue_counter_neg (global_uncorrected_pvalue_counter_neg) { - if (global_uncorrected_pvalue_counter_neg) - uncorrected_pvalue_counter_neg.reset (new std::vector(stats_calculator.num_elements(), 0)); + global_uncorrected_pvalue_counter_neg (global_uncorrected_pvalue_counter_neg) + { + if (global_uncorrected_pvalue_counter_neg) + uncorrected_pvalue_counter_neg.reset (new std::vector(stats_calculator.num_elements(), 0)); } @@ -166,41 +169,41 @@ namespace MR void process_permutation (size_t index) { - value_type max_stat = 0.0, min_stat = 0.0; + typename container_value_type::type max_stat = 0.0, min_stat = 0.0; stats_calculator (perm_stack.permutation (index), statistics, max_stat, min_stat); perm_dist_pos(index) = enhancer (max_stat, statistics, enhanced_statistics); - if (empirical_enhanced_statistics) { + if (empirical_enhanced_statistics.size()) { perm_dist_pos(index) = 0.0; - for (size_t i = 0; i < enhanced_statistics.size(); ++i) { - enhanced_statistics[i] /= (*empirical_enhanced_statistics)[i]; + for (size_t i = 0; i < size_t(enhanced_statistics.size()); ++i) { + enhanced_statistics[i] /= empirical_enhanced_statistics[i]; if (enhanced_statistics[i] > perm_dist_pos(index)) perm_dist_pos(index) = enhanced_statistics[i]; } } - for (size_t i = 0; i < enhanced_statistics.size(); ++i) { + for (size_t i = 0; i < size_t(enhanced_statistics.size()); ++i) { if (default_enhanced_statistics[i] > enhanced_statistics[i]) uncorrected_pvalue_counter[i]++; } // Compute the opposite contrast if (perm_dist_neg) { - for (size_t i = 0; i < statistics.size(); ++i) + for (size_t i = 0; i < size_t(statistics.size()); ++i) statistics[i] = -statistics[i]; (*perm_dist_neg)(index) = enhancer (-min_stat, statistics, enhanced_statistics); - if (empirical_enhanced_statistics) { + if (empirical_enhanced_statistics.size()) { (*perm_dist_neg)(index) = 0.0; - for (size_t i = 0; i < enhanced_statistics.size(); ++i) { - enhanced_statistics[i] /= (*empirical_enhanced_statistics)[i]; + for (size_t i = 0; i < size_t(enhanced_statistics.size()); ++i) { + enhanced_statistics[i] /= empirical_enhanced_statistics[i]; if (enhanced_statistics[i] > (*perm_dist_neg)(index)) (*perm_dist_neg)(index) = enhanced_statistics[i]; } } - for (size_t i = 0; i < enhanced_statistics.size(); ++i) { + for (size_t i = 0; i < size_t(enhanced_statistics.size()); ++i) { if ((*default_enhanced_statistics_neg)[i] > enhanced_statistics[i]) (*uncorrected_pvalue_counter_neg)[i]++; } @@ -211,15 +214,15 @@ namespace MR PermutationStack& perm_stack; StatsType stats_calculator; EnhancementType enhancer; - std::shared_ptr > empirical_enhanced_statistics; - const std::vector& default_enhanced_statistics; - const std::shared_ptr > default_enhanced_statistics_neg; - std::vector statistics; - std::vector enhanced_statistics; + const empirical_vector_type& empirical_enhanced_statistics; + const VectorType& default_enhanced_statistics; + const std::shared_ptr default_enhanced_statistics_neg; + VectorType statistics; + VectorType enhanced_statistics; std::vector uncorrected_pvalue_counter; std::shared_ptr > uncorrected_pvalue_counter_neg; - Eigen::Matrix& perm_dist_pos; - std::shared_ptr > perm_dist_neg; + VectorType& perm_dist_pos; + std::shared_ptr perm_dist_neg; std::vector& global_uncorrected_pvalue_counter; std::shared_ptr > global_uncorrected_pvalue_counter_neg; }; @@ -228,7 +231,7 @@ namespace MR // Precompute the empircal test statistic for non-stationarity adjustment template inline void precompute_empirical_stat (const StatsType& stats_calculator, const EnhancementType& enhancer, - size_t num_permutations, std::vector& empirical_statistic) + size_t num_permutations, empirical_vector_type& empirical_statistic) { std::vector global_enhanced_count (empirical_statistic.size(), 0); PermutationStack preprocessor_permutations (num_permutations, @@ -241,44 +244,44 @@ namespace MR } for (size_t i = 0; i < empirical_statistic.size(); ++i) { if (global_enhanced_count[i] > 0) - empirical_statistic[i] /= static_cast (global_enhanced_count[i]); + empirical_statistic[i] /= static_cast (global_enhanced_count[i]); } } // Precompute the default statistic image and enhanced statistic. We need to precompute this for calculating the uncorrected p-values. - template + template inline void precompute_default_permutation (const StatsType& stats_calculator, const EnhancementType& enhancer, - const std::shared_ptr >& empirical_enhanced_statistic, - std::vector& default_enhanced_statistics, std::shared_ptr >& default_enhanced_statistics_neg, - std::vector& default_statistics) + const empirical_vector_type empirical_enhanced_statistic, + VectorType& default_enhanced_statistics, std::shared_ptr default_enhanced_statistics_neg, + VectorType& default_statistics) { std::vector default_labelling (stats_calculator.num_subjects()); for (size_t i = 0; i < default_labelling.size(); ++i) default_labelling[i] = i; - value_type max_stat = 0.0, min_stat = 0.0; + typename container_value_type::type max_stat = 0.0, min_stat = 0.0; stats_calculator (default_labelling, default_statistics, max_stat, min_stat); max_stat = enhancer (max_stat, default_statistics, default_enhanced_statistics); - if (empirical_enhanced_statistic) { - for (size_t i = 0; i < default_statistics.size(); ++i) - default_enhanced_statistics[i] /= (*empirical_enhanced_statistic)[i]; + if (empirical_enhanced_statistic.size()) { + for (size_t i = 0; i < size_t(default_statistics.size()); ++i) + default_enhanced_statistics[i] /= empirical_enhanced_statistic[i]; } // Compute the opposite contrast if (default_enhanced_statistics_neg) { - for (size_t i = 0; i < default_statistics.size(); ++i) + for (size_t i = 0; i < size_t(default_statistics.size()); ++i) default_statistics[i] = -default_statistics[i]; max_stat = enhancer (-min_stat, default_statistics, *default_enhanced_statistics_neg); - if (empirical_enhanced_statistic) { - for (size_t i = 0; i < default_statistics.size(); ++i) - (*default_enhanced_statistics_neg)[i] /= (*empirical_enhanced_statistic)[i]; + if (empirical_enhanced_statistic.size()) { + for (size_t i = 0; i < size_t(default_statistics.size()); ++i) + (*default_enhanced_statistics_neg)[i] /= empirical_enhanced_statistic[i]; } // revert default_statistics to positive contrast for output - for (size_t i = 0; i < default_statistics.size(); ++i) + for (size_t i = 0; i < size_t(default_statistics.size()); ++i) default_statistics[i] = -default_statistics[i]; } } @@ -286,36 +289,36 @@ namespace MR - template + template inline void run_permutations (const StatsType& stats_calculator, const EnhancementType& enhancer, size_t num_permutations, - const std::shared_ptr >& empirical_enhanced_statistic, - const std::vector& default_enhanced_statistics, const std::shared_ptr >& default_enhanced_statistics_neg, - Eigen::Matrix& perm_dist_pos, std::shared_ptr >& perm_dist_neg, - std::vector& uncorrected_pvalues, std::shared_ptr >& uncorrected_pvalues_neg) + const empirical_vector_type& empirical_enhanced_statistic, + const VectorType& default_enhanced_statistics, const std::shared_ptr default_enhanced_statistics_neg, + VectorType& perm_dist_pos, std::shared_ptr perm_dist_neg, + VectorType& uncorrected_pvalues, std::shared_ptr uncorrected_pvalues_neg) { std::vector global_uncorrected_pvalue_count (stats_calculator.num_elements(), 0); - std::shared_ptr > global_uncorrected_pvalue_count_neg; + std::shared_ptr< std::vector > global_uncorrected_pvalue_count_neg; if (perm_dist_neg) - global_uncorrected_pvalue_count_neg.reset (new std::vector (stats_calculator.num_elements(), 0)); + global_uncorrected_pvalue_count_neg.reset (new std::vector (stats_calculator.num_elements(), 0)); { PermutationStack permutations (num_permutations, stats_calculator.num_subjects(), "running " + str(num_permutations) + " permutations..."); - Processor processor (permutations, stats_calculator, enhancer, - empirical_enhanced_statistic, - default_enhanced_statistics, default_enhanced_statistics_neg, - perm_dist_pos, perm_dist_neg, - global_uncorrected_pvalue_count, global_uncorrected_pvalue_count_neg); + Processor processor (permutations, stats_calculator, enhancer, + empirical_enhanced_statistic, + default_enhanced_statistics, default_enhanced_statistics_neg, + perm_dist_pos, perm_dist_neg, + global_uncorrected_pvalue_count, global_uncorrected_pvalue_count_neg); auto threads = Thread::run (Thread::multi (processor), "permutation threads"); } for (size_t i = 0; i < stats_calculator.num_elements(); ++i) { - uncorrected_pvalues[i] = static_cast (global_uncorrected_pvalue_count[i]) / static_cast (num_permutations); + uncorrected_pvalues[i] = global_uncorrected_pvalue_count[i] / default_type(num_permutations); if (perm_dist_neg) - (*uncorrected_pvalues_neg)[i] = static_cast ((*global_uncorrected_pvalue_count_neg)[i]) / static_cast (num_permutations); + (*uncorrected_pvalues_neg)[i] = (*global_uncorrected_pvalue_count_neg)[i] / default_type(num_permutations); } } diff --git a/src/stats/tfce.h b/src/stats/tfce.h index 62be83d0d2..1b7cb47c6f 100644 --- a/src/stats/tfce.h +++ b/src/stats/tfce.h @@ -27,6 +27,7 @@ namespace MR { typedef float value_type; + typedef Eigen::Array vector_type; /** \addtogroup Statistics @@ -37,22 +38,21 @@ namespace MR Enhancer (const Filter::Connector& connector, const value_type dh, const value_type E, const value_type H) : connector (connector), dh (dh), E (E), H (H) {} - value_type operator() (const value_type max_stat, const std::vector& stats, - std::vector& enhanced_stats) const + value_type operator() (const value_type max_stat, const vector_type& stats, + vector_type& enhanced_stats) const { - enhanced_stats.resize(stats.size()); - std::fill (enhanced_stats.begin(), enhanced_stats.end(), 0.0); + enhanced_stats = vector_type::Zero (stats.size()); for (value_type h = this->dh; h < max_stat; h += this->dh) { std::vector clusters; std::vector labels (enhanced_stats.size(), 0); connector.run (clusters, labels, stats, h); - for (size_t i = 0; i < enhanced_stats.size(); ++i) + for (size_t i = 0; i < size_t(enhanced_stats.size()); ++i) if (labels[i]) enhanced_stats[i] += pow (clusters[labels[i]-1].size, this->E) * pow (h, this->H); } - return *std::max_element (enhanced_stats.begin(), enhanced_stats.end()); + return enhanced_stats.maxCoeff(); } protected: From 91ed76359c387604652424aa319de94f598d942e Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 23 Jun 2016 16:43:15 +1000 Subject: [PATCH 005/723] Permutation testing: Fix race condition --- src/stats/permtest.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/stats/permtest.h b/src/stats/permtest.h index 772611084b..1801d6548f 100644 --- a/src/stats/permtest.h +++ b/src/stats/permtest.h @@ -142,7 +142,8 @@ namespace MR uncorrected_pvalue_counter (stats_calculator.num_elements(), 0), perm_dist_pos (perm_dist_pos), perm_dist_neg (perm_dist_neg), global_uncorrected_pvalue_counter (global_uncorrected_pvalue_counter), - global_uncorrected_pvalue_counter_neg (global_uncorrected_pvalue_counter_neg) + global_uncorrected_pvalue_counter_neg (global_uncorrected_pvalue_counter_neg), + mutex (new std::mutex()) { if (global_uncorrected_pvalue_counter_neg) uncorrected_pvalue_counter_neg.reset (new std::vector(stats_calculator.num_elements(), 0)); @@ -150,6 +151,7 @@ namespace MR ~Processor () { + std::lock_guard lock (*mutex); for (size_t i = 0; i < stats_calculator.num_elements(); ++i) { global_uncorrected_pvalue_counter[i] += uncorrected_pvalue_counter[i]; if (global_uncorrected_pvalue_counter_neg) @@ -223,8 +225,10 @@ namespace MR std::shared_ptr > uncorrected_pvalue_counter_neg; VectorType& perm_dist_pos; std::shared_ptr perm_dist_neg; + std::vector& global_uncorrected_pvalue_counter; std::shared_ptr > global_uncorrected_pvalue_counter_neg; + std::shared_ptr mutex; }; From 2972dcc87eddf3f09ee751160a4d17b9ecd09d46 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sun, 26 Jun 2016 03:27:16 +1000 Subject: [PATCH 006/723] Move src/mesh to src/surface Also corresponding change in namespace, which removes the ambiguity between the Mesh namespace and Mesh class, and is also a more appropriate name given future additions. --- cmd/label2mesh.cpp | 12 ++++++------ cmd/mesh2pve.cpp | 4 ++-- cmd/meshconvert.cpp | 6 +++--- cmd/meshfilter.cpp | 6 +++--- cmd/mrmesh.cpp | 10 +++++----- src/gui/mrview/tool/connectome/connectome.cpp | 5 ++--- src/gui/mrview/tool/connectome/connectome.h | 2 +- src/gui/mrview/tool/connectome/node.cpp | 4 ++-- src/gui/mrview/tool/connectome/node.h | 6 +++--- src/{mesh => surface}/mesh.cpp | 4 ++-- src/{mesh => surface}/mesh.h | 6 +++--- src/{mesh => surface}/vox2mesh.h | 8 ++++---- 12 files changed, 36 insertions(+), 37 deletions(-) rename src/{mesh => surface}/mesh.cpp (99%) rename src/{mesh => surface}/mesh.h (98%) rename src/{mesh => surface}/vox2mesh.h (99%) diff --git a/cmd/label2mesh.cpp b/cmd/label2mesh.cpp index bd323b9c47..c238822d9d 100644 --- a/cmd/label2mesh.cpp +++ b/cmd/label2mesh.cpp @@ -25,13 +25,13 @@ #include "algo/loop.h" #include "adapter/subset.h" -#include "mesh/mesh.h" -#include "mesh/vox2mesh.h" +#include "surface/mesh.h" +#include "surface/vox2mesh.h" using namespace MR; using namespace App; -using namespace MR::Mesh; +using namespace MR::Surface; void usage () @@ -87,7 +87,7 @@ void run () } } - MeshMulti meshes (lower_corners.size(), MR::Mesh::Mesh()); + MeshMulti meshes (lower_corners.size(), MR::Surface::Mesh()); meshes[0].set_name ("none"); const bool blocky = get_options ("blocky").size(); @@ -110,9 +110,9 @@ void run () scratch.value() = (subset.value() == in); if (blocky) - MR::Mesh::vox2mesh (scratch, meshes[in]); + MR::Surface::vox2mesh (scratch, meshes[in]); else - MR::Mesh::vox2mesh_mc (scratch, 0.5, meshes[in]); + MR::Surface::vox2mesh_mc (scratch, 0.5, meshes[in]); meshes[in].transform_voxel_to_realspace (scratch); meshes[in].set_name (str(in)); std::lock_guard lock (mutex); diff --git a/cmd/mesh2pve.cpp b/cmd/mesh2pve.cpp index fc376bdaa7..7d050fccd4 100644 --- a/cmd/mesh2pve.cpp +++ b/cmd/mesh2pve.cpp @@ -19,7 +19,7 @@ #include "header.h" -#include "mesh/mesh.h" +#include "surface/mesh.h" @@ -56,7 +56,7 @@ void run () { // Read in the mesh data - Mesh::Mesh mesh (argument[0]); + Surface::Mesh mesh (argument[0]); // Get the template image Header template_image = Header::open (argument[1]); diff --git a/cmd/meshconvert.cpp b/cmd/meshconvert.cpp index 4b3f26c2a6..f1899c9903 100644 --- a/cmd/meshconvert.cpp +++ b/cmd/meshconvert.cpp @@ -17,13 +17,13 @@ #include "command.h" #include "header.h" -#include "mesh/mesh.h" +#include "surface/mesh.h" using namespace MR; using namespace App; -using namespace MR::Mesh; +using namespace MR::Surface; @@ -71,7 +71,7 @@ void run () // Read in the mesh data MeshMulti meshes; try { - MR::Mesh::Mesh mesh (argument[0]); + MR::Surface::Mesh mesh (argument[0]); meshes.push_back (mesh); } catch (...) { meshes.load (argument[0]); diff --git a/cmd/meshfilter.cpp b/cmd/meshfilter.cpp index 862935d6eb..22b28a122f 100644 --- a/cmd/meshfilter.cpp +++ b/cmd/meshfilter.cpp @@ -19,7 +19,7 @@ #include "progressbar.h" #include "thread_queue.h" -#include "mesh/mesh.h" +#include "surface/mesh.h" @@ -30,7 +30,7 @@ using namespace MR; using namespace App; -using namespace MR::Mesh; +using namespace MR::Surface; const char* filters[] = { "smooth", NULL }; @@ -73,7 +73,7 @@ void run () // Read in the mesh data try { - MR::Mesh::Mesh mesh (argument[0]); + Mesh mesh (argument[0]); meshes.push_back (mesh); } catch (...) { meshes.load (argument[0]); diff --git a/cmd/mrmesh.cpp b/cmd/mrmesh.cpp index 95aca290c3..29d5fdca31 100644 --- a/cmd/mrmesh.cpp +++ b/cmd/mrmesh.cpp @@ -18,8 +18,8 @@ #include "image.h" #include "filter/optimal_threshold.h" -#include "mesh/mesh.h" -#include "mesh/vox2mesh.h" +#include "surface/mesh.h" +#include "surface/vox2mesh.h" @@ -53,18 +53,18 @@ void usage () void run () { - Mesh::Mesh mesh; + Surface::Mesh mesh; if (get_options ("blocky").size()) { auto input = Image::open (argument[0]); - Mesh::vox2mesh (input, mesh); + Surface::vox2mesh (input, mesh); } else { auto input = Image::open (argument[0]); float threshold = get_option_value ("threshold", Filter::estimate_optimal_threshold (input)); - Mesh::vox2mesh_mc (input, threshold, mesh); + Surface::vox2mesh_mc (input, threshold, mesh); } diff --git a/src/gui/mrview/tool/connectome/connectome.cpp b/src/gui/mrview/tool/connectome/connectome.cpp index 639ffb51da..682814ebaf 100644 --- a/src/gui/mrview/tool/connectome/connectome.cpp +++ b/src/gui/mrview/tool/connectome/connectome.cpp @@ -30,8 +30,7 @@ #include "dwi/tractography/file.h" #include "dwi/tractography/properties.h" -#include "mesh/mesh.h" -#include "mesh/vox2mesh.h" +#include "surface/vox2mesh.h" namespace MR { @@ -3784,7 +3783,7 @@ namespace MR // Request exemplar track file path from user const std::string path = GUI::Dialog::File::get_file (this, "Select file containing mesh for each node", "OBJ mesh files (*.obj)"); if (!path.size()) return; - Mesh::MeshMulti meshes; + Surface::MeshMulti meshes; meshes.load (path); if (meshes.size() != nodes.size()) throw Exception ("Mesh file contains " + str(meshes.size()) + " objects; expected " + str(nodes.size())); diff --git a/src/gui/mrview/tool/connectome/connectome.h b/src/gui/mrview/tool/connectome/connectome.h index f0bd30522a..9991785146 100644 --- a/src/gui/mrview/tool/connectome/connectome.h +++ b/src/gui/mrview/tool/connectome/connectome.h @@ -37,7 +37,7 @@ #include "gui/color_button.h" #include "gui/projection.h" -#include "mesh/mesh.h" +#include "surface/mesh.h" #include "connectome/mat2vec.h" #include "connectome/lut.h" diff --git a/src/gui/mrview/tool/connectome/node.cpp b/src/gui/mrview/tool/connectome/node.cpp index 02d34c1406..fb7d1e0575 100644 --- a/src/gui/mrview/tool/connectome/node.cpp +++ b/src/gui/mrview/tool/connectome/node.cpp @@ -20,7 +20,7 @@ #include "exception.h" #include "gui/mrview/window.h" -#include "mesh/vox2mesh.h" +#include "surface/vox2mesh.h" namespace MR { @@ -70,7 +70,7 @@ namespace MR - Node::Mesh::Mesh (MR::Mesh::Mesh& in) : + Node::Mesh::Mesh (MR::Surface::Mesh& in) : count (3 * in.num_triangles()) { MRView::GrabContext context; diff --git a/src/gui/mrview/tool/connectome/node.h b/src/gui/mrview/tool/connectome/node.h index 7ad5a6b8b9..374b448198 100644 --- a/src/gui/mrview/tool/connectome/node.h +++ b/src/gui/mrview/tool/connectome/node.h @@ -19,7 +19,7 @@ #include "image.h" #include "gui/opengl/gl.h" -#include "mesh/mesh.h" +#include "surface/mesh.h" namespace MR { @@ -38,7 +38,7 @@ namespace MR Node (const Eigen::Vector3f&, const size_t, const size_t, const MR::Image&); Node (); - void assign_mesh (MR::Mesh::Mesh& in) { clear_mesh(); mesh.reset (new Node::Mesh (in)); } + void assign_mesh (MR::Surface::Mesh& in) { clear_mesh(); mesh.reset (new Node::Mesh (in)); } void render_mesh() const { if (!mesh) return; mesh->render(); } void clear_mesh() { if (mesh) delete mesh.release(); } @@ -75,7 +75,7 @@ namespace MR // Helper class to manage the storage and display of the mesh for each node class Mesh { public: - Mesh (MR::Mesh::Mesh&); + Mesh (MR::Surface::Mesh&); Mesh (const Mesh&) = delete; Mesh (Mesh&&); Mesh () = delete; diff --git a/src/mesh/mesh.cpp b/src/surface/mesh.cpp similarity index 99% rename from src/mesh/mesh.cpp rename to src/surface/mesh.cpp index a0224a33cc..dfd29f6c8c 100644 --- a/src/mesh/mesh.cpp +++ b/src/surface/mesh.cpp @@ -14,7 +14,7 @@ */ -#include "mesh/mesh.h" +#include "surface/mesh.h" #include #include @@ -26,7 +26,7 @@ namespace MR { - namespace Mesh + namespace Surface { namespace { diff --git a/src/mesh/mesh.h b/src/surface/mesh.h similarity index 98% rename from src/mesh/mesh.h rename to src/surface/mesh.h index 3841253393..f66221f8c4 100644 --- a/src/mesh/mesh.h +++ b/src/surface/mesh.h @@ -13,8 +13,8 @@ * */ -#ifndef __mesh_mesh_h__ -#define __mesh_mesh_h__ +#ifndef __surface_mesh_h__ +#define __surface_mesh_h__ #include @@ -32,7 +32,7 @@ namespace MR { - namespace Mesh + namespace Surface { diff --git a/src/mesh/vox2mesh.h b/src/surface/vox2mesh.h similarity index 99% rename from src/mesh/vox2mesh.h rename to src/surface/vox2mesh.h index 95ee0dfec7..946518bbdd 100644 --- a/src/mesh/vox2mesh.h +++ b/src/surface/vox2mesh.h @@ -13,21 +13,21 @@ * */ -#ifndef __mesh_vox2mesh_h__ -#define __mesh_vox2mesh_h__ +#ifndef __surface_vox2mesh_h__ +#define __surface_vox2mesh_h__ #include #include #include "progressbar.h" -#include "mesh/mesh.h" +#include "surface/mesh.h" namespace MR { - namespace Mesh + namespace Surface { From 0c0b70db7feee565b860161ea489fd5d0c5ab737 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sun, 26 Jun 2016 03:49:57 +1000 Subject: [PATCH 007/723] Split contents of src/surface/mesh across multiple files --- cmd/label2mesh.cpp | 1 + cmd/meshconvert.cpp | 1 + cmd/meshfilter.cpp | 1 + src/gui/mrview/tool/connectome/connectome.cpp | 1 + src/surface/mesh.cpp | 168 +---------------- src/surface/mesh.h | 83 +------- src/surface/mesh_multi.cpp | 178 ++++++++++++++++++ src/surface/mesh_multi.h | 54 ++++++ src/surface/polygon.cpp | 42 +++++ src/surface/polygon.h | 75 ++++++++ src/surface/types.h | 54 ++++++ 11 files changed, 414 insertions(+), 244 deletions(-) create mode 100644 src/surface/mesh_multi.cpp create mode 100644 src/surface/mesh_multi.h create mode 100644 src/surface/polygon.cpp create mode 100644 src/surface/polygon.h create mode 100644 src/surface/types.h diff --git a/cmd/label2mesh.cpp b/cmd/label2mesh.cpp index c238822d9d..2b4e01d66f 100644 --- a/cmd/label2mesh.cpp +++ b/cmd/label2mesh.cpp @@ -26,6 +26,7 @@ #include "adapter/subset.h" #include "surface/mesh.h" +#include "surface/mesh_multi.h" #include "surface/vox2mesh.h" diff --git a/cmd/meshconvert.cpp b/cmd/meshconvert.cpp index f1899c9903..0e90ed0cc6 100644 --- a/cmd/meshconvert.cpp +++ b/cmd/meshconvert.cpp @@ -18,6 +18,7 @@ #include "command.h" #include "header.h" #include "surface/mesh.h" +#include "surface/mesh_multi.h" diff --git a/cmd/meshfilter.cpp b/cmd/meshfilter.cpp index 22b28a122f..51b48edd35 100644 --- a/cmd/meshfilter.cpp +++ b/cmd/meshfilter.cpp @@ -20,6 +20,7 @@ #include "thread_queue.h" #include "surface/mesh.h" +#include "surface/mesh_multi.h" diff --git a/src/gui/mrview/tool/connectome/connectome.cpp b/src/gui/mrview/tool/connectome/connectome.cpp index 682814ebaf..50b60aa7c7 100644 --- a/src/gui/mrview/tool/connectome/connectome.cpp +++ b/src/gui/mrview/tool/connectome/connectome.cpp @@ -30,6 +30,7 @@ #include "dwi/tractography/file.h" #include "dwi/tractography/properties.h" +#include "surface/mesh_multi.h" #include "surface/vox2mesh.h" namespace MR diff --git a/src/surface/mesh.cpp b/src/surface/mesh.cpp index dfd29f6c8c..6559da6795 100644 --- a/src/surface/mesh.cpp +++ b/src/surface/mesh.cpp @@ -29,26 +29,6 @@ namespace MR namespace Surface { - namespace { - // Little class needed for face data reading for OBJ files - struct FaceData { - uint32_t vertex, texture, normal; - }; - } - - - - template <> - bool Polygon<3>::shares_edge (const Polygon<3>& that) const - { - uint32_t shared_vertices = 0; - if (indices[0] == that[0] || indices[0] == that[1] || indices[0] == that[2]) ++shared_vertices; - if (indices[1] == that[0] || indices[1] == that[1] || indices[1] == that[2]) ++shared_vertices; - if (indices[2] == that[0] || indices[2] == that[1] || indices[2] == that[2]) ++shared_vertices; - return (shared_vertices == 2); - } - - Mesh::Mesh (const std::string& path) @@ -889,6 +869,11 @@ namespace MR void Mesh::load_obj (const std::string& path) { + + struct FaceData { + uint32_t vertex, texture, normal; + }; + std::ifstream in (path.c_str(), std::ios_base::in); if (!in) throw Exception ("Error opening input file!"); @@ -1217,149 +1202,6 @@ namespace MR - - - - - - - void MeshMulti::load (const std::string& path) - { - if (!Path::has_suffix (path, "obj") && !Path::has_suffix (path, "OBJ")) - throw Exception ("Multiple meshes only supported by OBJ file format"); - - std::ifstream in (path.c_str(), std::ios_base::in); - if (!in) - throw Exception ("Error opening input file!"); - std::string line; - std::string object; - int index = -1; - int counter = -1; - VertexList vertices; - TriangleList triangles; - QuadList quads; - size_t vertex_index_offset = 1; - while (std::getline (in, line)) { - // TODO Functionalise most of the below - ++counter; - if (!line.size()) continue; - if (line[0] == '#') continue; - const size_t divider = line.find_first_of (' '); - const std::string prefix (line.substr (0, divider)); - std::string data (line.substr (divider+1, line.npos)); - if (prefix == "v") { - if (index < 0) - throw Exception ("Malformed OBJ file; vertex outside object (line " + str(counter) + ")"); - float values[4]; - sscanf (data.c_str(), "%f %f %f %f", &values[0], &values[1], &values[2], &values[3]); - vertices.push_back (Vertex (values[0], values[1], values[2])); - } else if (prefix == "vt") { - } else if (prefix == "vn") { - //if (index < 0) - // throw Exception ("Malformed OBJ file; vertex normal outside object (line " + str(counter) + ")"); - //float values[3]; - //sscanf (data.c_str(), "%f %f %f", &values[0], &values[1], &values[2]); - //normals.push_back (Vertex (values[0], values[1], values[2])); - } else if (prefix == "vp") { - } else if (prefix == "f") { - if (index < 0) - throw Exception ("Malformed OBJ file; face outside object (line " + str(counter) + ")"); - std::vector elements; - do { - const size_t first_space = data.find_first_of (' '); - if (first_space == data.npos) { - if (std::isalnum (data[0])) - elements.push_back (data); - data.clear(); - } else { - elements.push_back (data.substr (0, first_space)); - data = data.substr (first_space+1); - } - } while (data.size()); - if (elements.size() != 3 && elements.size() != 4) - throw Exception ("Malformed face information in input OBJ file (face with neither 3 nor 4 vertices; line " + str(counter) + ")"); - std::vector face_data; - size_t values_per_element = 0; - for (std::vector::iterator i = elements.begin(); i != elements.end(); ++i) { - FaceData temp; - temp.vertex = 0; temp.texture = 0; temp.normal = 0; - const size_t first_slash = i->find_first_of ('/'); - temp.vertex = to (i->substr (0, first_slash)) - vertex_index_offset; - size_t this_values_count = 0; - if (first_slash == i->npos) { - this_values_count = 1; - } else { - const size_t last_slash = i->find_last_of ('/'); - if (last_slash == first_slash) { - temp.texture = to (i->substr (last_slash+1)) - vertex_index_offset; - this_values_count = 2; - } else { - temp.texture = to (i->substr (first_slash, last_slash)) - vertex_index_offset; - temp.normal = to (i->substr (last_slash+1)) - vertex_index_offset; - this_values_count = 3; - } - } - if (!values_per_element) - values_per_element = this_values_count; - else if (values_per_element != this_values_count) - throw Exception ("Malformed face information in input OBJ file (inconsistent vertex / texture / normal detail); line " + str(counter)); - face_data.push_back (temp); - } - if (face_data.size() == 3) { - std::vector temp { face_data[0].vertex, face_data[1].vertex, face_data[2].vertex }; - triangles.push_back (Triangle (temp)); - } else { - std::vector temp { face_data[0].vertex, face_data[1].vertex, face_data[2].vertex, face_data[3].vertex }; - quads.push_back (Quad (temp)); - } - } else if (prefix == "g") { - } else if (prefix == "o") { - // This is where this function differs from the standard OBJ load - // Allow multiple objects; in fact explicitly expect them - if (index++ >= 0) { - vertex_index_offset += vertices.size(); - Mesh temp; - temp.load (std::move (vertices), std::move (triangles), std::move (quads)); - temp.set_name (object.size() ? object : str(index-1)); - push_back (temp); - } - object = data; - } - } - - if (vertices.size()) { - ++index; - Mesh temp; - temp.load (vertices, triangles, quads); - temp.set_name (object); - push_back (temp); - } - } - - void MeshMulti::save (const std::string& path) const - { - if (!Path::has_suffix (path, "obj") && !Path::has_suffix (path, "OBJ")) - throw Exception ("Multiple meshes only supported by OBJ file format"); - File::OFStream out (path); - size_t offset = 1; - out << "# mrtrix_version: " << App::mrtrix_version << "\n"; - for (const_iterator i = begin(); i != end(); ++i) { - out << "o " << i->get_name() << "\n"; - for (VertexList::const_iterator v = i->vertices.begin(); v != i->vertices.end(); ++v) - out << "v " << str((*v)[0]) << " " << str((*v)[1]) << " " << str((*v)[2]) << " 1.0\n"; - for (TriangleList::const_iterator t = i->triangles.begin(); t != i->triangles.end(); ++t) - out << "f " << str((*t)[0]+offset) << " " << str((*t)[1]+offset) << " " << str((*t)[2]+offset) << "\n"; - for (QuadList::const_iterator q = i->quads.begin(); q != i->quads.end(); ++q) - out << "f " << str((*q)[0]+offset) << " " << str((*q)[1]+offset) << " " << str((*q)[2]+offset) << " " << str((*q)[3]+offset) << "\n"; - offset += i->vertices.size(); - } - } - - - - - - } } diff --git a/src/surface/mesh.h b/src/surface/mesh.h index f66221f8c4..b5f32fcc6a 100644 --- a/src/surface/mesh.h +++ b/src/surface/mesh.h @@ -28,6 +28,8 @@ #include "algo/copy.h" #include "algo/loop.h" +#include "surface/types.h" + namespace MR @@ -36,67 +38,6 @@ namespace MR { - typedef Eigen::Vector3 Vertex; - typedef std::vector VertexList; - - class Vox : public Eigen::Array3i - { - public: - using Eigen::Array3i::Array3i; - bool operator< (const Vox& i) const - { - return ((*this)[2] == i[2] ? (((*this)[1] == i[1]) ? ((*this)[0] < i[0]) : ((*this)[1] < i[1])) : ((*this)[2] < i[2])); - } - }; - - - template - class Polygon - { - - public: - - template - Polygon (const T* const d) - { - for (size_t i = 0; i != vertices; ++i) - indices[i] = d[i]; - } - - template - Polygon (const C& d) - { - assert (d.size() == vertices); - for (size_t i = 0; i != vertices; ++i) - indices[i] = d[i]; - } - - Polygon() - { - memset (indices, 0, vertices * sizeof (uint32_t)); - } - - - uint32_t& operator[] (const size_t i) { assert (i < vertices); return indices[i]; } - uint32_t operator[] (const size_t i) const { assert (i < vertices); return indices[i]; } - - size_t size() const { return vertices; } - - bool shares_edge (const Polygon&) const; - - private: - uint32_t indices[vertices]; - - }; - - template <> bool Polygon<3>::shares_edge (const Polygon<3>&) const; - - typedef Polygon<3> Triangle; - typedef std::vector TriangleList; - typedef Polygon<4> Quad; - typedef std::vector QuadList; - - class Mesh { @@ -224,26 +165,6 @@ namespace MR - - // Class to handle multiple meshes per file - // For now, this will only be supported using the .obj file type - // TODO Another alternative may be .vtp: XML-based polydata by VTK - // (would allow embedding binary data within the file, rather than - // everything being ASCII as in .obj) - - class MeshMulti : public std::vector - { - public: - using std::vector::vector; - - void load (const std::string&); - void save (const std::string&) const; - }; - - - - - } } diff --git a/src/surface/mesh_multi.cpp b/src/surface/mesh_multi.cpp new file mode 100644 index 0000000000..5c68327bd6 --- /dev/null +++ b/src/surface/mesh_multi.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + +#include "surface/mesh_multi.h" + +#include +#include + + + +namespace MR +{ + namespace Surface + { + + + + void MeshMulti::load (const std::string& path) + { + + struct FaceData { + uint32_t vertex, texture, normal; + }; + + if (!Path::has_suffix (path, "obj") && !Path::has_suffix (path, "OBJ")) + throw Exception ("Multiple meshes only supported by OBJ file format"); + + std::ifstream in (path.c_str(), std::ios_base::in); + if (!in) + throw Exception ("Error opening input file!"); + std::string line; + std::string object; + int index = -1; + int counter = -1; + VertexList vertices; + TriangleList triangles; + QuadList quads; + size_t vertex_index_offset = 1; + while (std::getline (in, line)) { + // TODO Functionalise most of the below + ++counter; + if (!line.size()) continue; + if (line[0] == '#') continue; + const size_t divider = line.find_first_of (' '); + const std::string prefix (line.substr (0, divider)); + std::string data (line.substr (divider+1, line.npos)); + if (prefix == "v") { + if (index < 0) + throw Exception ("Malformed OBJ file; vertex outside object (line " + str(counter) + ")"); + float values[4]; + sscanf (data.c_str(), "%f %f %f %f", &values[0], &values[1], &values[2], &values[3]); + vertices.push_back (Vertex (values[0], values[1], values[2])); + } else if (prefix == "vt") { + } else if (prefix == "vn") { + //if (index < 0) + // throw Exception ("Malformed OBJ file; vertex normal outside object (line " + str(counter) + ")"); + //float values[3]; + //sscanf (data.c_str(), "%f %f %f", &values[0], &values[1], &values[2]); + //normals.push_back (Vertex (values[0], values[1], values[2])); + } else if (prefix == "vp") { + } else if (prefix == "f") { + if (index < 0) + throw Exception ("Malformed OBJ file; face outside object (line " + str(counter) + ")"); + std::vector elements; + do { + const size_t first_space = data.find_first_of (' '); + if (first_space == data.npos) { + if (std::isalnum (data[0])) + elements.push_back (data); + data.clear(); + } else { + elements.push_back (data.substr (0, first_space)); + data = data.substr (first_space+1); + } + } while (data.size()); + if (elements.size() != 3 && elements.size() != 4) + throw Exception ("Malformed face information in input OBJ file (face with neither 3 nor 4 vertices; line " + str(counter) + ")"); + std::vector face_data; + size_t values_per_element = 0; + for (std::vector::iterator i = elements.begin(); i != elements.end(); ++i) { + FaceData temp; + temp.vertex = 0; temp.texture = 0; temp.normal = 0; + const size_t first_slash = i->find_first_of ('/'); + temp.vertex = to (i->substr (0, first_slash)) - vertex_index_offset; + size_t this_values_count = 0; + if (first_slash == i->npos) { + this_values_count = 1; + } else { + const size_t last_slash = i->find_last_of ('/'); + if (last_slash == first_slash) { + temp.texture = to (i->substr (last_slash+1)) - vertex_index_offset; + this_values_count = 2; + } else { + temp.texture = to (i->substr (first_slash, last_slash)) - vertex_index_offset; + temp.normal = to (i->substr (last_slash+1)) - vertex_index_offset; + this_values_count = 3; + } + } + if (!values_per_element) + values_per_element = this_values_count; + else if (values_per_element != this_values_count) + throw Exception ("Malformed face information in input OBJ file (inconsistent vertex / texture / normal detail); line " + str(counter)); + face_data.push_back (temp); + } + if (face_data.size() == 3) { + std::vector temp { face_data[0].vertex, face_data[1].vertex, face_data[2].vertex }; + triangles.push_back (Triangle (temp)); + } else { + std::vector temp { face_data[0].vertex, face_data[1].vertex, face_data[2].vertex, face_data[3].vertex }; + quads.push_back (Quad (temp)); + } + } else if (prefix == "g") { + } else if (prefix == "o") { + // This is where this function differs from the standard OBJ load + // Allow multiple objects; in fact explicitly expect them + if (index++ >= 0) { + vertex_index_offset += vertices.size(); + Mesh temp; + temp.load (std::move (vertices), std::move (triangles), std::move (quads)); + temp.set_name (object.size() ? object : str(index-1)); + push_back (temp); + } + object = data; + } + } + + if (vertices.size()) { + ++index; + Mesh temp; + temp.load (vertices, triangles, quads); + temp.set_name (object); + push_back (temp); + } + } + + + + void MeshMulti::save (const std::string& path) const + { + if (!Path::has_suffix (path, "obj") && !Path::has_suffix (path, "OBJ")) + throw Exception ("Multiple meshes only supported by OBJ file format"); + File::OFStream out (path); + size_t offset = 1; + out << "# mrtrix_version: " << App::mrtrix_version << "\n"; + for (const_iterator i = begin(); i != end(); ++i) { + out << "o " << i->get_name() << "\n"; + for (VertexList::const_iterator v = i->vertices.begin(); v != i->vertices.end(); ++v) + out << "v " << str((*v)[0]) << " " << str((*v)[1]) << " " << str((*v)[2]) << " 1.0\n"; + for (TriangleList::const_iterator t = i->triangles.begin(); t != i->triangles.end(); ++t) + out << "f " << str((*t)[0]+offset) << " " << str((*t)[1]+offset) << " " << str((*t)[2]+offset) << "\n"; + for (QuadList::const_iterator q = i->quads.begin(); q != i->quads.end(); ++q) + out << "f " << str((*q)[0]+offset) << " " << str((*q)[1]+offset) << " " << str((*q)[2]+offset) << " " << str((*q)[3]+offset) << "\n"; + offset += i->vertices.size(); + } + } + + + + + + + } +} + + diff --git a/src/surface/mesh_multi.h b/src/surface/mesh_multi.h new file mode 100644 index 0000000000..10e8b64fee --- /dev/null +++ b/src/surface/mesh_multi.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __surface_mesh_multi_h__ +#define __surface_mesh_multi_h__ + + +#include + +#include "surface/mesh.h" + + + +namespace MR +{ + namespace Surface + { + + + + // Class to handle multiple meshes per file + // For now, this will only be supported using the .obj file type + // TODO Another alternative may be .vtp: XML-based polydata by VTK + // (would allow embedding binary data within the file, rather than + // everything being ASCII as in .obj) + + class MeshMulti : public std::vector + { + public: + using std::vector::vector; + + void load (const std::string&); + void save (const std::string&) const; + }; + + + + } +} + +#endif + diff --git a/src/surface/polygon.cpp b/src/surface/polygon.cpp new file mode 100644 index 0000000000..612e4311ce --- /dev/null +++ b/src/surface/polygon.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + +#include "surface/polygon.h" + + +namespace MR +{ + namespace Surface + { + + + + template <> + bool Polygon<3>::shares_edge (const Polygon<3>& that) const + { + uint32_t shared_vertices = 0; + if (indices[0] == that[0] || indices[0] == that[1] || indices[0] == that[2]) ++shared_vertices; + if (indices[1] == that[0] || indices[1] == that[1] || indices[1] == that[2]) ++shared_vertices; + if (indices[2] == that[0] || indices[2] == that[1] || indices[2] == that[2]) ++shared_vertices; + return (shared_vertices == 2); + } + + + + } +} + + diff --git a/src/surface/polygon.h b/src/surface/polygon.h new file mode 100644 index 0000000000..a67689408e --- /dev/null +++ b/src/surface/polygon.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __surface_polygon_h__ +#define __surface_polygon_h__ + +#include +#include + +namespace MR +{ + namespace Surface + { + + + template + class Polygon + { + + public: + + template + Polygon (const T* const d) + { + for (size_t i = 0; i != vertices; ++i) + indices[i] = d[i]; + } + + template + Polygon (const C& d) + { + assert (d.size() == vertices); + for (size_t i = 0; i != vertices; ++i) + indices[i] = d[i]; + } + + Polygon() + { + memset (indices, 0, vertices * sizeof (uint32_t)); + } + + + uint32_t& operator[] (const size_t i) { assert (i < vertices); return indices[i]; } + uint32_t operator[] (const size_t i) const { assert (i < vertices); return indices[i]; } + + size_t size() const { return vertices; } + + bool shares_edge (const Polygon&) const; + + private: + uint32_t indices[vertices]; + + }; + + template <> bool Polygon<3>::shares_edge (const Polygon<3>&) const; + + + + } +} + +#endif + diff --git a/src/surface/types.h b/src/surface/types.h new file mode 100644 index 0000000000..47d097089e --- /dev/null +++ b/src/surface/types.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __surface_types_h__ +#define __surface_types_h__ + + + +#include "surface/polygon.h" + + + +namespace MR +{ + namespace Surface + { + + + typedef Eigen::Vector3 Vertex; + typedef std::vector VertexList; + typedef Polygon<3> Triangle; + typedef Polygon<4> Quad; + typedef std::vector TriangleList; + typedef std::vector QuadList; + + class Vox : public Eigen::Array3i + { + public: + using Eigen::Array3i::Array3i; + bool operator< (const Vox& i) const + { + return ((*this)[2] == i[2] ? (((*this)[1] == i[1]) ? ((*this)[0] < i[0]) : ((*this)[1] < i[1])) : ((*this)[2] < i[2])); + } + }; + + + + } +} + +#endif + From 529c20ba0cfcbde295654ec0792a18c13629fd66 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sun, 26 Jun 2016 08:03:08 +1000 Subject: [PATCH 008/723] Move code out of Surface::Mesh class into Surface::Algo and Surface::Filter --- cmd/label2mesh.cpp | 8 +- cmd/mesh2pve.cpp | 8 +- cmd/meshconvert.cpp | 73 +-- cmd/meshfilter.cpp | 51 +- cmd/mrmesh.cpp | 8 +- docs/reference/commands/meshconvert.rst | 13 +- src/gui/mrview/tool/connectome/connectome.cpp | 1 - src/gui/mrview/tool/connectome/node.cpp | 1 - src/surface/{vox2mesh.h => algo/image2mesh.h} | 69 ++- src/surface/algo/mesh2image.cpp | 315 ++++++++++ src/surface/algo/mesh2image.h | 42 ++ src/surface/filter/base.cpp | 50 ++ src/surface/filter/base.h | 72 +++ src/surface/filter/smooth.cpp | 195 ++++++ src/surface/filter/smooth.h | 74 +++ src/surface/filter/vertex_transform.cpp | 102 ++++ src/surface/filter/vertex_transform.h | 72 +++ src/surface/mesh.cpp | 571 +----------------- src/surface/mesh.h | 60 +- src/surface/utils.h | 79 +++ testing/tests/meshconvert | 8 +- 21 files changed, 1168 insertions(+), 704 deletions(-) rename src/surface/{vox2mesh.h => algo/image2mesh.h} (93%) create mode 100644 src/surface/algo/mesh2image.cpp create mode 100644 src/surface/algo/mesh2image.h create mode 100644 src/surface/filter/base.cpp create mode 100644 src/surface/filter/base.h create mode 100644 src/surface/filter/smooth.cpp create mode 100644 src/surface/filter/smooth.h create mode 100644 src/surface/filter/vertex_transform.cpp create mode 100644 src/surface/filter/vertex_transform.h create mode 100644 src/surface/utils.h diff --git a/cmd/label2mesh.cpp b/cmd/label2mesh.cpp index 2b4e01d66f..d3495ce206 100644 --- a/cmd/label2mesh.cpp +++ b/cmd/label2mesh.cpp @@ -15,6 +15,7 @@ +#include #include #include "command.h" @@ -27,7 +28,7 @@ #include "surface/mesh.h" #include "surface/mesh_multi.h" -#include "surface/vox2mesh.h" +#include "surface/algo/image2mesh.h" using namespace MR; @@ -111,10 +112,9 @@ void run () scratch.value() = (subset.value() == in); if (blocky) - MR::Surface::vox2mesh (scratch, meshes[in]); + MR::Surface::Algo::image2mesh_blocky (scratch, meshes[in]); else - MR::Surface::vox2mesh_mc (scratch, 0.5, meshes[in]); - meshes[in].transform_voxel_to_realspace (scratch); + MR::Surface::Algo::image2mesh_mc (scratch, meshes[in], 0.5); meshes[in].set_name (str(in)); std::lock_guard lock (mutex); ++progress; diff --git a/cmd/mesh2pve.cpp b/cmd/mesh2pve.cpp index 7d050fccd4..9bc2836b91 100644 --- a/cmd/mesh2pve.cpp +++ b/cmd/mesh2pve.cpp @@ -20,6 +20,7 @@ #include "header.h" #include "surface/mesh.h" +#include "surface/algo/mesh2image.h" @@ -59,9 +60,12 @@ void run () Surface::Mesh mesh (argument[0]); // Get the template image - Header template_image = Header::open (argument[1]); + Header template_header = Header::open (argument[1]); // Create the output image - mesh.output_pve_image (template_image, argument[2]); + Image output = Image::create (argument[2], template_header); + + // Perform the partial volume estimation + Surface::Algo::mesh2image (mesh, output); } diff --git a/cmd/meshconvert.cpp b/cmd/meshconvert.cpp index 0e90ed0cc6..17227fb6a5 100644 --- a/cmd/meshconvert.cpp +++ b/cmd/meshconvert.cpp @@ -19,6 +19,7 @@ #include "header.h" #include "surface/mesh.h" #include "surface/mesh_multi.h" +#include "surface/filter/vertex_transform.h" @@ -28,20 +29,7 @@ using namespace MR::Surface; -const OptionGroup transform_options = OptionGroup ("Options for applying spatial transformations to vertices") - - + Option ("transform_first2real", "transform vertices from FSL FIRST's native corrdinate space to real space") - + Argument ("image").type_image_in() - - + Option ("transform_real2first", "transform vertices from FSL real space to FIRST's native corrdinate space") - + Argument ("image").type_image_in() - - + Option ("transform_voxel2real", "transform vertices from voxel space to real space") - + Argument ("image").type_image_in() - - + Option ("transform_real2voxel", "transform vertices from real space to voxel space") - + Argument ("image").type_image_in(); - +const char* transform_choices[] = { "first2real", "real2first", "voxel2real", "real2voxel", nullptr }; @@ -58,9 +46,12 @@ void usage () + Argument ("output", "the output mesh file").type_file_out(); OPTIONS - + Option ("binary", "write the output file in binary format") + + Option ("binary", "write the output mesh file in binary format (if supported)") - + transform_options; + + 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(); } @@ -78,44 +69,20 @@ void run () meshes.load (argument[0]); } - bool have_transformed = false; - - auto opt = get_options ("transform_first2real"); - if (opt.size()) { - Header H = Header::open (opt[0][0]); - for (auto i = meshes.begin(); i != meshes.end(); ++i) - i->transform_first_to_realspace (H); - have_transformed = true; - } - - opt = get_options ("transform_real2first"); - if (opt.size()) { - if (have_transformed) - throw Exception ("meshconvert can only perform one spatial transformation per call"); - Header H = Header::open (opt[0][0]); - for (auto i = meshes.begin(); i != meshes.end(); ++i) - i->transform_realspace_to_first (H); - have_transformed = true; - } - - opt = get_options ("transform_voxel2real"); - if (opt.size()) { - if (have_transformed) - throw Exception ("meshconvert can only perform one spatial transformation per call"); - Header H = Header::open (opt[0][0]); - for (auto i = meshes.begin(); i != meshes.end(); ++i) - i->transform_voxel_to_realspace (H); - have_transformed = true; - } - - opt = get_options ("transform_real2voxel"); + auto opt = get_options ("transform"); if (opt.size()) { - if (have_transformed) - throw Exception ("meshconvert can only perform one spatial transformation per call"); - Header H = Header::open (opt[0][0]); - for (auto i = meshes.begin(); i != meshes.end(); ++i) - i->transform_realspace_to_voxel (H); - have_transformed = true; + auto H = Header::open (opt[0][2]); + std::unique_ptr transform (new Surface::Filter::VertexTransform (H)); + switch (int(opt[0][1])) { + case 0: transform->set_first2real(); break; + case 1: transform->set_real2first(); break; + case 2: transform->set_voxel2real(); break; + case 3: transform->set_real2voxel(); break; + default: throw Exception ("Unexpected mode for spatial transformation of vertices"); + } + MeshMulti temp; + (*transform) (meshes, temp); + std::swap (meshes, temp); } // Create the output file diff --git a/cmd/meshfilter.cpp b/cmd/meshfilter.cpp index 51b48edd35..e375dc1953 100644 --- a/cmd/meshfilter.cpp +++ b/cmd/meshfilter.cpp @@ -21,11 +21,8 @@ #include "surface/mesh.h" #include "surface/mesh_multi.h" - - - -#define DEFAULT_SMOOTHING_SPATIAL 10.0 -#define DEFAULT_SMOOTHING_INFLUENCE 10.0 +#include "surface/filter/base.h" +#include "surface/filter/smooth.h" @@ -39,10 +36,10 @@ const char* filters[] = { "smooth", NULL }; const OptionGroup smooth_option = OptionGroup ("Options for mesh smoothing filter") - + Option ("smooth_spatial", "spatial extent of smoothing (default: " + str(DEFAULT_SMOOTHING_INFLUENCE, 2) + "mm)") + + 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(DEFAULT_SMOOTHING_INFLUENCE, 2) + ")") + + Option ("smooth_influence", "influence factor for smoothing (default: " + str(Filter::default_smoothing_influence_factor, 2) + ")") + Argument ("value").type_float (0.0); @@ -70,41 +67,37 @@ void usage () void run () { - MeshMulti meshes; + MeshMulti in; // Read in the mesh data try { Mesh mesh (argument[0]); - meshes.push_back (mesh); + in.push_back (mesh); } catch (...) { - meshes.load (argument[0]); + in.load (argument[0]); } - // Apply the relevant filter - int filter = argument[1]; - if (filter == 0) { - - const float spatial = get_option_value ("smooth_spatial", DEFAULT_SMOOTHING_SPATIAL); - const float influence = get_option_value ("smooth_inluence", DEFAULT_SMOOTHING_INFLUENCE); - - if (meshes.size() == 1) { - meshes.front().smooth (spatial, influence); - } else { - std::mutex mutex; - ProgressBar progress ("Applying smoothing filter to multiple meshes", meshes.size()); - auto loader = [&] (size_t& out) { static size_t i = 0; out = i++; return (out != meshes.size()); }; - auto worker = [&] (const size_t& in) { meshes[in].smooth (spatial, influence); std::lock_guard lock (mutex); ++progress; return true; }; - Thread::run_queue (loader, size_t(), Thread::multi (worker)); - } + MeshMulti out; + // Apply the relevant filter + 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 std::string msg = in.size() > 1 ? "Applying smoothing filter to multiple meshes" : ""; + filter.reset (new Filter::Smooth (msg, spatial, influence)); } else { assert (0); } + out.assign (in.size(), Mesh()); + (*filter) (in, out); + // Create the output file - if (meshes.size() == 1) - meshes.front().save (argument[2]); + if (out.size() == 1) + out.front().save (argument[2]); else - meshes.save (argument[2]); + out.save (argument[2]); } diff --git a/cmd/mrmesh.cpp b/cmd/mrmesh.cpp index 29d5fdca31..e8dcf531c0 100644 --- a/cmd/mrmesh.cpp +++ b/cmd/mrmesh.cpp @@ -19,7 +19,7 @@ #include "image.h" #include "filter/optimal_threshold.h" #include "surface/mesh.h" -#include "surface/vox2mesh.h" +#include "surface/algo/image2mesh.h" @@ -58,13 +58,13 @@ void run () if (get_options ("blocky").size()) { auto input = Image::open (argument[0]); - Surface::vox2mesh (input, mesh); + Surface::Algo::image2mesh_blocky (input, mesh); } else { auto input = Image::open (argument[0]); - float threshold = get_option_value ("threshold", Filter::estimate_optimal_threshold (input)); - Surface::vox2mesh_mc (input, threshold, mesh); + const default_type threshold = get_option_value ("threshold", Filter::estimate_optimal_threshold (input)); + Surface::Algo::image2mesh_mc (input, mesh, threshold); } diff --git a/docs/reference/commands/meshconvert.rst b/docs/reference/commands/meshconvert.rst index a7564bac84..bd62ee10ef 100644 --- a/docs/reference/commands/meshconvert.rst +++ b/docs/reference/commands/meshconvert.rst @@ -21,18 +21,9 @@ convert meshes between different formats, and apply transformations. Options ------- -- **-binary** write the output file in binary format +- **-binary** write the output mesh file in binary format (if supported) -Options for applying spatial transformations to vertices -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -- **-transform_first2real image** transform vertices from FSL FIRST's native corrdinate space to real space - -- **-transform_real2first image** transform vertices from FSL real space to FIRST's native corrdinate space - -- **-transform_voxel2real image** transform vertices from voxel space to real space - -- **-transform_real2voxel image** transform vertices from real space to voxel space +- **-transform mode image** transform vertices from one coordinate space to another, based on a template image; options are: first2real, real2first, voxel2real, real2voxel Standard options ^^^^^^^^^^^^^^^^ diff --git a/src/gui/mrview/tool/connectome/connectome.cpp b/src/gui/mrview/tool/connectome/connectome.cpp index 50b60aa7c7..854564eb5c 100644 --- a/src/gui/mrview/tool/connectome/connectome.cpp +++ b/src/gui/mrview/tool/connectome/connectome.cpp @@ -31,7 +31,6 @@ #include "dwi/tractography/properties.h" #include "surface/mesh_multi.h" -#include "surface/vox2mesh.h" namespace MR { diff --git a/src/gui/mrview/tool/connectome/node.cpp b/src/gui/mrview/tool/connectome/node.cpp index fb7d1e0575..a6b802968e 100644 --- a/src/gui/mrview/tool/connectome/node.cpp +++ b/src/gui/mrview/tool/connectome/node.cpp @@ -20,7 +20,6 @@ #include "exception.h" #include "gui/mrview/window.h" -#include "surface/vox2mesh.h" namespace MR { diff --git a/src/surface/vox2mesh.h b/src/surface/algo/image2mesh.h similarity index 93% rename from src/surface/vox2mesh.h rename to src/surface/algo/image2mesh.h index 946518bbdd..3128e684e8 100644 --- a/src/surface/vox2mesh.h +++ b/src/surface/algo/image2mesh.h @@ -13,15 +13,18 @@ * */ -#ifndef __surface_vox2mesh_h__ -#define __surface_vox2mesh_h__ +#ifndef __surface_algo_image2mesh_h__ +#define __surface_algo_image2mesh_h__ #include #include +#include -#include "progressbar.h" +#include "image_helpers.h" +#include "transform.h" #include "surface/mesh.h" +#include "surface/types.h" @@ -29,14 +32,16 @@ namespace MR { namespace Surface { + namespace Algo + { - // Function to convert a binary image into a mesh - // Note that this produces a 'hard mesh' that follows the voxel boundaries exactly; - // it therefore appears very 'blocky' - template - void vox2mesh (const ImageType& input_image, Mesh& out) - { + // Function to convert a binary image into a mesh + // Note that this produces a mesh that follows the voxel boundaries exactly; + // it therefore appears very 'blocky' + template + void image2mesh_blocky (const ImageType& input_image, Mesh& out) + { static const Vox steps[6] = { Vox ( 0, 0, -1), Vox ( 0, -1, 0), @@ -55,24 +60,25 @@ namespace MR if (input_image.ndim() != 3) throw Exception ("Voxel-to-mesh conversion only works for 3D images"); - ImageType in (input_image), neighbour (input_image); + ImageType voxel (input_image), neighbour (input_image); VertexList vertices; - TriangleList polygons; - std::map< Vox, size_t > vox2vertindex; + TriangleList triangles; + std::map vox2vertindex; // Perform all initial calculations in voxel space; - // only after the data has been written to a Mesh class will the conversion to - // real space take place - // + // only once the final vertex position in voxel space is determined + // is it transformed to real space for the final output + Transform transform (input_image); + // Also, for initial calculations, do this such that a voxel location actually // refers to the lower corner of the voxel; that way searches for existing // vertices can be done using a simple map Vox pos; - for (auto loop = Loop(in) (in); loop; ++loop) { - if (in.value()) { + for (auto loop = Loop(voxel) (voxel); loop; ++loop) { + if (voxel.value()) { - assign_pos_of (in).to (pos); + assign_pos_of (voxel).to (pos); for (size_t adj = 0; adj != 6; ++adj) { @@ -115,12 +121,13 @@ namespace MR const auto existing = vox2vertindex.find (voxels[in_vertex]); if (existing == vox2vertindex.end()) { triangle_vertices[out_vertex] = vertices.size(); - vertices.push_back (Vertex (float(voxels[in_vertex][0]) - 0.5f, float(voxels[in_vertex][1]) - 0.5f, float(voxels[in_vertex][2]) - 0.5f)); + Eigen::Vector3 pos_voxelspace (default_type(voxels[in_vertex][0]) - 0.5, default_type(voxels[in_vertex][1]) - 0.5, default_type(voxels[in_vertex][2]) - 0.5); + vertices.push_back (transform.voxel2scanner * pos_voxelspace); } else { triangle_vertices[out_vertex] = existing->second; } } - polygons.push_back (triangle_vertices); + triangles.push_back (triangle_vertices); } } // End checking for interface @@ -129,15 +136,15 @@ namespace MR } } // End looping over image // Write the result to the output class - out.load (vertices, polygons); + out.load (vertices, triangles); } - // vox2mesh function using the Marching Cubes algorithm + // Image-to-mesh conversion function using the Marching Cubes algorithm template - void vox2mesh_mc (const ImageType& input_image, const float threshold, Mesh& out) + void image2mesh_mc (const ImageType& input_image, Mesh& out, const default_type threshold) { static const Vox neighbour_offsets[] = { Vox (0, 0, 0), Vox (1, 0, 0), @@ -430,7 +437,9 @@ namespace MR {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1} }; VertexList vertices; - TriangleList polygons; + TriangleList triangles; + + Transform transform (input_image); ImageType voxel (input_image); float in_vertex_values[8]; @@ -488,9 +497,9 @@ namespace MR edge_to_output_vertex[edge_index] = vertices.size(); // Calculate the precise position of this vertex, based on the // image intensities in the two relevant voxels - const float alpha = (threshold - in_vertex_values[vertex_indices[0]]) / (in_vertex_values[vertex_indices[1]] - in_vertex_values[vertex_indices[0]]); - const Vertex pos = vertex_positions[0].cast() + ((1.0f - alpha) * (vertex_positions[1] - vertex_positions[0]).cast()); - vertices.push_back (pos); + const default_type alpha = (threshold - in_vertex_values[vertex_indices[0]]) / (in_vertex_values[vertex_indices[1]] - in_vertex_values[vertex_indices[0]]); + const Vertex pos_voxelspace = vertex_positions[0].cast() + ((1.0 - alpha) * (vertex_positions[1] - vertex_positions[0]).cast()); + vertices.push_back (transform.voxel2scanner * pos_voxelspace); } else { edge_to_output_vertex[edge_index] = existing_zero->second; } @@ -506,18 +515,18 @@ namespace MR // to calculate the correct surface normals for (const int8_t* first_edge = cube_triangle_table[code]; *first_edge >= 0; first_edge += 3) { const uint32_t indices[3] { edge_to_output_vertex[*first_edge], edge_to_output_vertex[*(first_edge+2)], edge_to_output_vertex[*(first_edge+1)] }; - polygons.push_back (Triangle (indices)); + triangles.push_back (Triangle (indices)); } } } } // Finished looping over all voxels in the input image // Write the result to the output class - out.load (vertices, polygons); + out.load (vertices, triangles); } - + } } } diff --git a/src/surface/algo/mesh2image.cpp b/src/surface/algo/mesh2image.cpp new file mode 100644 index 0000000000..04697987b8 --- /dev/null +++ b/src/surface/algo/mesh2image.cpp @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "surface/algo/mesh2image.h" + +#include +#include + +#include "header.h" +#include "progressbar.h" + +#include "surface/types.h" +#include "surface/utils.h" +#include "surface/filter/vertex_transform.h" + +namespace MR +{ + namespace Surface + { + namespace Algo + { + + + + void mesh2image (const Mesh& mesh_realspace, Image& image) + { + + // For initial segmentation of mesh - identify voxels on the mesh, inside & outside + enum vox_mesh_t { UNDEFINED, ON_MESH, OUTSIDE, INSIDE }; + + ProgressBar progress ("converting mesh to PVE image", 6); + + // For speed, want the vertex data to be in voxel positions + Filter::VertexTransform transform (image); + transform.set_real2voxel(); + Mesh mesh; + transform (mesh_realspace, mesh); + + static const Vox adj_voxels[6] = { { -1, 0, 0 }, + { +1, 0, 0 }, + { 0, -1, 0 }, + { 0, +1, 0 }, + { 0, 0, -1 }, + { 0, 0, +1 } }; + + // Compute normals for polygons + std::vector polygon_normals; + polygon_normals.reserve (mesh.num_polygons()); + for (TriangleList::const_iterator p = mesh.get_triangles().begin(); p != mesh.get_triangles().end(); ++p) + polygon_normals.push_back (normal (mesh, *p)); + for (QuadList::const_iterator p = mesh.get_quads().begin(); p != mesh.get_quads().end(); ++p) + polygon_normals.push_back (normal (mesh, *p)); + ++progress; + + // Create some memory to work with: + // Stores a flag for each voxel as encoded in enum vox_mesh_t + Header H (image); + auto init_seg = Image::scratch (H); + + // For every voxel, stores those polygons that may intersect the voxel + typedef std::map< Vox, std::vector > Vox2Poly; + Vox2Poly voxel2poly; + + // Map each polygon to the underlying voxels + for (size_t poly_index = 0; poly_index != mesh.num_polygons(); ++poly_index) { + + const size_t num_vertices = (poly_index < mesh.num_triangles()) ? 3 : 4; + + // Figure out the voxel extent of this polygon in three dimensions + Vox lower_bound (H.size(0)-1, H.size(1)-1, H.size(2)-1), upper_bound (0, 0, 0); + VertexList this_poly_verts; + if (num_vertices == 3) + mesh.load_triangle_vertices (this_poly_verts, poly_index); + else + mesh.load_quad_vertices (this_poly_verts, poly_index - mesh.num_triangles()); + for (VertexList::const_iterator v = this_poly_verts.begin(); v != this_poly_verts.end(); ++v) { + for (size_t axis = 0; axis != 3; ++axis) { + const int this_axis_voxel = std::round((*v)[axis]); + lower_bound[axis] = std::min (lower_bound[axis], this_axis_voxel); + upper_bound[axis] = std::max (upper_bound[axis], this_axis_voxel); + } + } + + // Constrain to lie within the dimensions of the image + for (size_t axis = 0; axis != 3; ++axis) { + lower_bound[axis] = std::max(0, lower_bound[axis]); + upper_bound[axis] = std::min(int(H.size(axis)-1), upper_bound[axis]); + } + + // For all voxels within this rectangular region, assign this polygon to the map + Vox voxel; + for (voxel[2] = lower_bound[2]; voxel[2] <= upper_bound[2]; ++voxel[2]) { + for (voxel[1] = lower_bound[1]; voxel[1] <= upper_bound[1]; ++voxel[1]) { + for (voxel[0] = lower_bound[0]; voxel[0] <= upper_bound[0]; ++voxel[0]) { + std::vector this_voxel_polys; + //#if __clang__ + // Vox2Poly::const_iterator existing = voxel2poly.find (voxel); + //#else + // Vox2Poly::iterator existing = voxel2poly.find (voxel); + //#endif + Vox2Poly::const_iterator existing = voxel2poly.find (voxel); + if (existing != voxel2poly.end()) { + this_voxel_polys = existing->second; + voxel2poly.erase (existing); + } else { + // Only call this once each voxel, regardless of the number of intersecting polygons + assign_pos_of (voxel).to (init_seg); + init_seg.value() = ON_MESH; + } + this_voxel_polys.push_back (poly_index); + voxel2poly.insert (std::make_pair (voxel, this_voxel_polys)); + } } } + + } + ++progress; + + + // Find all voxels that are not partial-volumed with the mesh, and are not inside the mesh + // Use a corner of the image FoV to commence filling of the volume, and then check that all + // eight corners have been flagged as outside the volume + const Vox corner_voxels[8] = { + Vox ( 0, 0, 0), + Vox ( 0, 0, H.size (2) - 1), + Vox ( 0, H.size (1) - 1, 0), + Vox ( 0, H.size (1) - 1, H.size (2) - 1), + Vox (H.size (0) - 1, 0, 0), + Vox (H.size (0) - 1, 0, H.size (2) - 1), + Vox (H.size (0) - 1, H.size (1) - 1, 0), + Vox (H.size (0) - 1, H.size (1) - 1, H.size (2) - 1)}; + + // TODO This is slow; is there a faster implementation? + // This is essentially a connected-component analysis... + std::vector to_expand; + to_expand.push_back (corner_voxels[0]); + assign_pos_of (corner_voxels[0]).to (init_seg); + init_seg.value() = vox_mesh_t::OUTSIDE; + do { + const Vox centre_voxel (to_expand.back()); + to_expand.pop_back(); + for (size_t adj_vox_idx = 0; adj_vox_idx != 6; ++adj_vox_idx) { + const Vox this_voxel (centre_voxel + adj_voxels[adj_vox_idx]); + assign_pos_of (this_voxel).to (init_seg); + if (!is_out_of_bounds (init_seg) && init_seg.value() == vox_mesh_t (UNDEFINED)) { + init_seg.value() = vox_mesh_t (OUTSIDE); + to_expand.push_back (this_voxel); + } + } + } while (!to_expand.empty()); + ++progress; + + for (size_t cnr_idx = 0; cnr_idx != 8; ++cnr_idx) { + assign_pos_of (corner_voxels[cnr_idx]).to (init_seg); + if (init_seg.value() == vox_mesh_t (UNDEFINED)) + throw Exception ("Mesh is not bound within image field of view"); + } + + + // Find those voxels that remain unassigned, and set them to INSIDE + for (auto l = Loop (init_seg) (init_seg); l; ++l) { + if (init_seg.value() == vox_mesh_t (UNDEFINED)) + init_seg.value() = vox_mesh_t (INSIDE); + } + ++progress; + + // Write initial ternary segmentation + for (auto l = Loop (init_seg) (init_seg, image); l; ++l) { + switch (init_seg.value()) { + case vox_mesh_t (UNDEFINED): throw Exception ("Code error: poor filling of initial mesh estimate"); break; + case vox_mesh_t (ON_MESH): image.value() = 0.5; break; + case vox_mesh_t (OUTSIDE): image.value() = 0.0; break; + case vox_mesh_t (INSIDE): image.value() = 1.0; break; + } + } + ++progress; + + // Get better partial volume estimates for all necessary voxels + // TODO This could be multi-threaded, but hard to justify the dev time + static const size_t pve_os_ratio = 10; + + for (Vox2Poly::const_iterator i = voxel2poly.begin(); i != voxel2poly.end(); ++i) { + + const Vox& voxel (i->first); + + // Generate a set of points within this voxel that need to be tested individually + std::vector to_test; + to_test.reserve (Math::pow3 (pve_os_ratio)); + for (size_t x_idx = 0; x_idx != pve_os_ratio; ++x_idx) { + const default_type x = voxel[0] - 0.5 + ((default_type(x_idx) + 0.5) / default_type(pve_os_ratio)); + for (size_t y_idx = 0; y_idx != pve_os_ratio; ++y_idx) { + const default_type y = voxel[1] - 0.5 + ((default_type(y_idx) + 0.5) / default_type(pve_os_ratio)); + for (size_t z_idx = 0; z_idx != pve_os_ratio; ++z_idx) { + const default_type z = voxel[2] - 0.5 + ((default_type(z_idx) + 0.5) / default_type(pve_os_ratio)); + to_test.push_back (Vertex (x, y, z)); + } + } + } + + // Count the number of these points that lie inside the mesh + int inside_mesh_count = 0; + for (std::vector::const_iterator i_p = to_test.begin(); i_p != to_test.end(); ++i_p) { + const Vertex& p (*i_p); + + default_type best_min_edge_distance = -std::numeric_limits::infinity(); + bool best_result_inside = false; + + // Only test against those polygons that are near this voxel + for (std::vector::const_iterator polygon_index = i->second.begin(); polygon_index != i->second.end(); ++polygon_index) { + const Eigen::Vector3& n (polygon_normals[*polygon_index]); + + const size_t polygon_num_vertices = (*polygon_index < mesh.num_triangles()) ? 3 : 4; + VertexList v; + + bool is_inside = false; + default_type min_edge_distance = std::numeric_limits::infinity(); + + if (polygon_num_vertices == 3) { + + mesh.load_triangle_vertices (v, *polygon_index); + + // First: is it aligned with the normal? + const Vertex poly_centre ((v[0] + v[1] + v[2]) * (1.0/3.0)); + const Vertex diff (p - poly_centre); + is_inside = (diff.dot (n) <= 0.0); + + // Second: how well does it project onto this polygon? + const Vertex p_on_plane (p - (n * (diff.dot (n)))); + + std::array edge_distances; + Vertex zero = (v[2]-v[0]).cross (n); zero.normalize(); + Vertex one = (v[1]-v[2]).cross (n); one .normalize(); + Vertex two = (v[0]-v[1]).cross (n); two .normalize(); + edge_distances[0] = (p_on_plane-v[0]).dot (zero); + edge_distances[1] = (p_on_plane-v[2]).dot (one); + edge_distances[2] = (p_on_plane-v[1]).dot (two); + min_edge_distance = std::min (edge_distances[0], std::min (edge_distances[1], edge_distances[2])); + + } else { + + mesh.load_quad_vertices (v, *polygon_index); + + // This may be slightly ill-posed with a quad; no guarantee of fixed normal + // Proceed regardless + + // First: is it aligned with the normal? + const Vertex poly_centre ((v[0] + v[1] + v[2] + v[3]) * 0.25); + const Vertex diff (p - poly_centre); + is_inside = (diff.dot (n) <= 0.0); + + // Second: how well does it project onto this polygon? + const Vertex p_on_plane (p - (n * (diff.dot (n)))); + + for (int edge = 0; edge != 4; ++edge) { + // Want an appropriate vector emanating from this edge from which to test the 'on-plane' distance + // (bearing in mind that there may not be a uniform normal) + // For this, I'm going to take a weighted average based on the relative distance between the + // two points at either end of this edge + // Edge is between points p1 and p2; edge 0 is between points 0 and 1 + const Vertex& p0 ((edge-1) >= 0 ? v[edge-1] : v[3]); + const Vertex& p1 (v[edge]); + const Vertex& p2 ((edge+1) < 4 ? v[edge+1] : v[0]); + const Vertex& p3 ((edge+2) < 4 ? v[edge+2] : v[edge-2]); + + const default_type d1 = (p1 - p_on_plane).norm(); + const default_type d2 = (p2 - p_on_plane).norm(); + // Give more weight to the normal at the point that's closer + Vertex edge_normal = (d2*(p0-p1) + d1*(p3-p2)); + edge_normal.normalize(); + + // Now, how far away is the point within the plane from this edge? + const default_type this_edge_distance = (p_on_plane - p1).dot (edge_normal); + min_edge_distance = std::min (min_edge_distance, this_edge_distance); + + } + + } + + if (min_edge_distance > best_min_edge_distance) { + best_min_edge_distance = min_edge_distance; + best_result_inside = is_inside; + } + + } + + if (best_result_inside) + ++inside_mesh_count; + + } + + assign_pos_of (voxel).to (image); + image.value() = (default_type)inside_mesh_count / (default_type)Math::pow3 (pve_os_ratio); + + } + + } + + + + + } + } +} + + diff --git a/src/surface/algo/mesh2image.h b/src/surface/algo/mesh2image.h new file mode 100644 index 0000000000..661654eca3 --- /dev/null +++ b/src/surface/algo/mesh2image.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __surface_algo_mesh2image_h__ +#define __surface_algo_mesh2image_h__ + + +#include "image.h" +#include "types.h" +#include "surface/mesh.h" + + + +namespace MR +{ + namespace Surface + { + namespace Algo + { + + + void mesh2image (const Mesh&, Image&); + + + } + } +} + +#endif + diff --git a/src/surface/filter/base.cpp b/src/surface/filter/base.cpp new file mode 100644 index 0000000000..475ca7ad6b --- /dev/null +++ b/src/surface/filter/base.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "surface/filter/base.h" + +#include +#include + +#include "thread_queue.h" + +namespace MR +{ + namespace Surface + { + namespace Filter + { + + + + void Base::operator() (const MeshMulti& in, MeshMulti& out) const + { + std::unique_ptr progress; + if (message.size()) + progress.reset (new ProgressBar (message, in.size())); + out.assign (in.size(), Mesh()); + + std::mutex mutex; + auto loader = [&] (size_t& index) { static size_t i = 0; index = i++; return (index != in.size()); }; + auto worker = [&] (const size_t& index) { (*this) (in[index], out[index]); if (progress) { std::lock_guard lock (mutex); ++(*progress); } return true; }; + Thread::run_queue (loader, size_t(), Thread::multi (worker)); + } + + + + } + } +} + diff --git a/src/surface/filter/base.h b/src/surface/filter/base.h new file mode 100644 index 0000000000..d8268ee33d --- /dev/null +++ b/src/surface/filter/base.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __surface_filter_base_h__ +#define __surface_filter_base_h__ + +#include "progressbar.h" // May be needed for any derived classes that make use of the message string + +#include "surface/mesh.h" +#include "surface/mesh_multi.h" + +namespace MR +{ + namespace Surface + { + namespace Filter + { + + /*! A base class for defining surface mesh filters. + * + * The Surface::Filter::Base class defines the basic + * interface for defining filters that operate upon mesh + * data. It allows these filters to be initialised, + * set up and run using base class pointers, and defines a + * standardised functor interface that mesh filter classes + * should ideally conform to. + * + */ + class Base + { + public: + Base (const std::string& s) : + message (s) { } + Base () { } + + virtual ~Base() { } + + void set_message (const std::string& s) { message = s; } + + virtual void operator() (const Mesh&, Mesh&) const + { + throw Exception ("Running empty function Surface::Filter::Base::operator()"); + } + + virtual void operator() (const MeshMulti&, MeshMulti&) const; + + protected: + std::string message; + + }; + //! @} + + + + } + } +} + + +#endif diff --git a/src/surface/filter/smooth.cpp b/src/surface/filter/smooth.cpp new file mode 100644 index 0000000000..4771c06028 --- /dev/null +++ b/src/surface/filter/smooth.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "surface/filter/smooth.h" + +#include + +#include "surface/utils.h" + +namespace MR +{ + namespace Surface + { + namespace Filter + { + + + void Smooth::operator() (const Mesh& in, Mesh& out) const + { + std::unique_ptr progress; + if (message.size()) + progress.reset (new ProgressBar (message, 8)); + out.clear(); + + const size_t V = in.num_vertices(); + if (!V) return; + + if (in.num_quads()) + throw Exception ("For now, mesh smoothing is only supported for triangular meshes"); + const size_t T = in.num_triangles(); + if (V == 3*T) + throw Exception ("Cannot perform smoothing on this mesh: no triangulation information"); + + // Pre-compute polygon centroids and areas + VertexList centroids; + std::vector areas; + for (TriangleList::const_iterator p = in.triangles.begin(); p != in.triangles.end(); ++p) { + centroids.push_back ((in.vertices[(*p)[0]] + in.vertices[(*p)[1]] + in.vertices[(*p)[2]]) * (1.0/3.0)); + areas.push_back (area (in, *p)); + } + if (progress) ++(*progress); + + // Perform pre-calculation of an appropriate mesh neighbourhood for each vertex + // Use knowledge of the connections between vertices provided by the triangles/quads to + // perform an iterative search outward from each vertex, selecting a subset of + // polygons for each vertex + // Extent of window should be approximately the value of spatial_factor, though only an + // approximate windowing is likely to be used (i.e. number of iterations) + // + // Initialisation is different to iterations: Need a single pass to find those + // polygons that actually use the vertex + std::vector< std::set > vert_polys (V, std::set()); + // For each vertex, don't just want to store the polygons within the neighbourhood; + // also want to store those that will be expanded from in the next iteration + std::vector< std::vector > vert_polys_to_expand (V, std::vector()); + + for (uint32_t t = 0; t != T; ++t) { + for (uint32_t i = 0; i != 3; ++i) { + vert_polys[(in.triangles[t])[i]].insert (t); + vert_polys_to_expand[(in.triangles[t])[i]].push_back (t); + } + } + if (progress) ++(*progress); + + // Now, we want to expand this selection outwards for each vertex + // To do this, also want to produce a list for each polygon: containing those polygons + // that share a common edge (i.e. two vertices) + std::vector< std::vector > poly_neighbours (T, std::vector()); + for (uint32_t i = 0; i != T; ++i) { + for (uint32_t j = i+1; j != T; ++j) { + if (in.triangles[i].shares_edge (in.triangles[j])) { + poly_neighbours[i].push_back (j); + poly_neighbours[j].push_back (i); + + } + } + } + if (progress) ++(*progress); + + // TODO Will want to develop a better heuristic for this + for (size_t iter = 0; iter != 8; ++iter) { + for (uint32_t v = 0; v != V; ++v) { + + // Find polygons at the outer edge of this expanding front, and add them to the neighbourhood for this vertex + std::vector next_front; + for (std::vector::const_iterator front = vert_polys_to_expand[v].begin(); front != vert_polys_to_expand[v].end(); ++front) { + for (std::vector::const_iterator expansion = poly_neighbours[*front].begin(); expansion != poly_neighbours[*front].end(); ++expansion) { + const std::set::const_iterator existing = vert_polys[v].find (*expansion); + if (existing == vert_polys[v].end()) { + vert_polys[v].insert (*expansion); + next_front.push_back (*expansion); + } + } + } + vert_polys_to_expand[v] = std::move (next_front); + + } + } + if (progress) ++(*progress); + + + + // Need to perform a first mollification pass, where the polygon normals are + // smoothed but the vertices are not perturbed + // However, in order to calculate these new normals, we need to calculate new vertex positions! + VertexList mollified_vertices; + // Use half standard spatial factor for mollification + // Denominator = 2(SF/2)^2 + const default_type spatial_mollification_power_multiplier = -2.0 / Math::pow2 (spatial); + // No need to normalise the Gaussian; have to explicitly normalise afterwards + for (uint32_t v = 0; v != V; ++v) { + + Vertex new_pos (0.0, 0.0, 0.0); + default_type sum_weights = 0.0; + + // For now, just use every polygon as part of the estimate + // Eventually, restrict this to some form of mesh neighbourhood + //for (size_t i = 0; i != centroids.size(); ++i) { + for (std::set::const_iterator it = vert_polys[v].begin(); it != vert_polys[v].end(); ++it) { + const uint32_t i = *it; + default_type this_weight = areas[i]; + const default_type distance_sq = (centroids[i] - in.vertices[v]).squaredNorm(); + this_weight *= std::exp (distance_sq * spatial_mollification_power_multiplier); + const Vertex prediction = centroids[i]; + new_pos += this_weight * prediction; + sum_weights += this_weight; + } + + new_pos *= (1.0 / sum_weights); + mollified_vertices.push_back (new_pos); + + } + if (progress) ++(*progress); + + // Have new vertices; compute polygon normals based on these vertices + Mesh mollified_mesh; + mollified_mesh.load (mollified_vertices, in.triangles); + VertexList tangents; + for (TriangleList::const_iterator p = mollified_mesh.triangles.begin(); p != mollified_mesh.triangles.end(); ++p) + tangents.push_back (normal (mollified_mesh, *p)); + if (progress) ++(*progress); + + // Now perform the actual smoothing + const default_type spatial_power_multiplier = -0.5 / Math::pow2 (spatial); + const default_type influence_power_multiplier = -0.5 / Math::pow2 (influence); + for (size_t v = 0; v != V; ++v) { + + Vertex new_pos (0.0, 0.0, 0.0); + default_type sum_weights = 0.0; + + //for (size_t i = 0; i != centroids.size(); ++i) { + for (std::set::const_iterator it = vert_polys[v].begin(); it != vert_polys[v].end(); ++it) { + const uint32_t i = *it; + default_type this_weight = areas[i]; + const default_type distance_sq = (centroids[i] - in.vertices[v]).squaredNorm(); + this_weight *= std::exp (distance_sq * spatial_power_multiplier); + const default_type prediction_distance = (centroids[i] - in.vertices[v]).dot (tangents[i]); + const Vertex prediction = in.vertices[v] + (tangents[i] * prediction_distance); + this_weight *= std::exp (Math::pow2 (prediction_distance) * influence_power_multiplier); + new_pos += this_weight * prediction; + sum_weights += this_weight; + } + + new_pos *= (1.0 / sum_weights); + out.vertices.push_back (new_pos); + + } + if (progress) ++(*progress); + + out.triangles = in.triangles; + + // If the vertex normals were calculated previously, re-calculate them + if (in.have_normals()) + out.calculate_normals(); + + } + + + + } + } +} + diff --git a/src/surface/filter/smooth.h b/src/surface/filter/smooth.h new file mode 100644 index 0000000000..1054992238 --- /dev/null +++ b/src/surface/filter/smooth.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __surface_filter_smooth_h__ +#define __surface_filter_smooth_h__ + +#include "surface/mesh.h" +#include "surface/filter/base.h" + + +namespace MR +{ + namespace Surface + { + namespace Filter + { + + + + constexpr default_type default_smoothing_spatial_factor = 10.0; + constexpr default_type default_smoothing_influence_factor = 10.0; + + + + class Smooth : public Base + { + public: + Smooth () : + Base (), + spatial (default_smoothing_spatial_factor), + influence (default_smoothing_influence_factor) { } + + Smooth (const std::string& s) : + Base (s), + spatial (default_smoothing_spatial_factor), + influence (default_smoothing_influence_factor) { } + + Smooth (const default_type spatial_factor, const default_type influence_factor) : + Base (), + spatial (spatial_factor), + influence (influence_factor) { } + + Smooth (const std::string& s, const default_type spatial_factor, const default_type influence_factor) : + Base (s), + spatial (spatial_factor), + influence (influence_factor) { } + + void operator() (const Mesh&, Mesh&) const override; + + private: + default_type spatial, influence; + + }; + + + + } + } +} + + +#endif diff --git a/src/surface/filter/vertex_transform.cpp b/src/surface/filter/vertex_transform.cpp new file mode 100644 index 0000000000..834fd2bcd9 --- /dev/null +++ b/src/surface/filter/vertex_transform.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + +#include "surface/filter/vertex_transform.h" + +#include "exception.h" + + +namespace MR +{ + namespace Surface + { + namespace Filter + { + + + + void VertexTransform::operator() (const Mesh& in, Mesh& out) const + { + VertexList vertices, normals; + const size_t V = in.num_vertices(); + vertices.reserve (V); + if (in.have_normals()) + normals.reserve (V); + switch (mode) { + + case transform_t::UNDEFINED: + throw Exception ("Error: VertexTransform must have the transform type set"); + + case transform_t::FIRST2REAL: + for (size_t i = 0; i != V; ++i) { + Vertex v = in.vert(i); + v[0] = ((header.size(0)-1) * header.spacing(0)) - v[0]; + vertices.push_back (transform.image2scanner * v); + } + if (in.have_normals()) { + for (size_t i = 0; i != V; ++i) { + Vertex n = in.norm(i); + n[0] = -n[0]; + normals.push_back (transform.image2scanner.rotation() * n); + } + } + break; + + case transform_t::REAL2FIRST: + for (size_t i = 0; i != V; ++i) { + Vertex v = in.vert(i); + v = transform.scanner2image * v; + v[0] = ((header.size(0)-1) * header.spacing(0)) - v[0]; + } + if (in.have_normals()) { + for (size_t i = 0; i != V; ++i) { + Vertex n = transform.scanner2image.rotation() * in.norm(i); + n[0] = -n[0]; + normals.push_back (std::move (n)); + } + } + break; + + case transform_t::VOXEL2REAL: + for (size_t i = 0; i != V; ++i) + vertices.push_back (transform.voxel2scanner * in.vert(i)); + if (in.have_normals()) { + for (size_t i = 0; i != V; ++i) + normals.push_back (transform.voxel2scanner.rotation() * in.norm(i)); + } + break; + + case transform_t::REAL2VOXEL: + for (size_t i = 0; i != V; ++i) + vertices.push_back (transform.scanner2voxel * in.vert(i)); + if (in.have_normals()) { + for (size_t i = 0; i != V; ++i) + normals.push_back (transform.scanner2voxel.rotation() * in.norm(i)); + } + break; + + } + + out.load (vertices, normals, in.get_triangles(), in.get_quads()); + } + + + + } + } +} + + diff --git a/src/surface/filter/vertex_transform.h b/src/surface/filter/vertex_transform.h new file mode 100644 index 0000000000..e1f00c64ba --- /dev/null +++ b/src/surface/filter/vertex_transform.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __surface_filter_vertex_transform_h__ +#define __surface_filter_vertex_transform_h__ + +#include "header.h" +#include "transform.h" + +#include "surface/mesh.h" +#include "surface/mesh_multi.h" +#include "surface/filter/base.h" + + + +namespace MR +{ + namespace Surface + { + namespace Filter + { + + + class VertexTransform : public Base + { + public: + enum class transform_t { UNDEFINED, FIRST2REAL, REAL2FIRST, VOXEL2REAL, REAL2VOXEL }; + + VertexTransform (const Header& H) : + header (H), + transform (H), + mode (transform_t::UNDEFINED) { } + + void set_first2real() { mode = transform_t::FIRST2REAL; } + void set_real2first() { mode = transform_t::REAL2FIRST; } + void set_voxel2real() { mode = transform_t::VOXEL2REAL; } + void set_real2voxel() { mode = transform_t::REAL2VOXEL; } + + transform_t get_mode() const { return mode; } + + void operator() (const Mesh&, Mesh&) const override; + + void operator() (const MeshMulti& in, MeshMulti& out) const { + Base::operator() (in, out); + } + + private: + const Header& header; + Transform transform; + transform_t mode; + + }; + + + } + } +} + +#endif + diff --git a/src/surface/mesh.cpp b/src/surface/mesh.cpp index 6559da6795..e468b6f739 100644 --- a/src/surface/mesh.cpp +++ b/src/surface/mesh.cpp @@ -19,9 +19,9 @@ #include #include -#include #include -#include + +#include "surface/utils.h" namespace MR @@ -46,58 +46,6 @@ namespace MR - void Mesh::transform_first_to_realspace (const Header& header) - { - Transform transform (header); - for (VertexList::iterator v = vertices.begin(); v != vertices.end(); ++v) { - (*v)[0] = ((header.size(0)-1) * header.spacing(0)) - (*v)[0]; - *v = transform.image2scanner * *v; - } - if (normals.size()) { - for (VertexList::iterator n = normals.begin(); n != normals.end(); ++n) { - (*n)[0] = -(*n)[0]; - *n = transform.image2scanner.rotation() * *n; - } - } - } - - void Mesh::transform_realspace_to_first (const Header& header) - { - Transform transform (header); - for (VertexList::iterator v = vertices.begin(); v != vertices.end(); ++v) { - *v = transform.scanner2image * *v; - (*v)[0] = ((header.size(0)-1) * header.spacing(0)) - (*v)[0]; - } - if (normals.size()) { - for (VertexList::iterator n = normals.begin(); n != normals.end(); ++n) { - *n = transform.scanner2image.rotation() * *n; - (*n)[0] = -(*n)[0]; - } - } - } - - void Mesh::transform_voxel_to_realspace (const Header& header) - { - Transform transform (header); - for (VertexList::iterator v = vertices.begin(); v != vertices.end(); ++v) - *v = transform.voxel2scanner * *v; - if (normals.size()) { - for (VertexList::iterator n = normals.begin(); n != normals.end(); ++n) - *n = transform.voxel2scanner.rotation() * *n; - } - } - - void Mesh::transform_realspace_to_voxel (const Header& header) - { - Transform transform (header); - for (VertexList::iterator v = vertices.begin(); v != vertices.end(); ++v) - *v = transform.scanner2voxel * *v; - if (normals.size()) { - for (VertexList::iterator n = normals.begin(); n != normals.end(); ++n) - *n = transform.scanner2voxel.rotation() * *n; - } - } - void Mesh::save (const std::string& path, const bool binary) const @@ -115,455 +63,17 @@ namespace MR - void Mesh::output_pve_image (const Header& H, const std::string& path) - { - - // For initial segmentation of mesh - identify voxels on the mesh, inside & outside - enum vox_mesh_t { UNDEFINED, ON_MESH, OUTSIDE, INSIDE }; - - ProgressBar progress ("converting mesh to PVE image", 7); - - // For speed, want the vertex data to be in voxel positions - // Therefore modify the vertex data in place, but save the original data and set it - // back to the way it was on function completion - const VertexList vertices_realspace (vertices); - transform_realspace_to_voxel (H); - - static const Vox adj_voxels[6] = { { -1, 0, 0 }, - { +1, 0, 0 }, - { 0, -1, 0 }, - { 0, +1, 0 }, - { 0, 0, -1 }, - { 0, 0, +1 } }; - - // Compute normals for polygons - std::vector polygon_normals; - normals.reserve (triangles.size() + quads.size()); - for (TriangleList::const_iterator p = triangles.begin(); p != triangles.end(); ++p) - polygon_normals.push_back (calc_normal (*p)); - for (QuadList::const_iterator p = quads.begin(); p != quads.end(); ++p) - polygon_normals.push_back (calc_normal (*p)); - - // Create some memory to work with: - // Stores a flag for each voxel as encoded in enum vox_mesh_t - auto init_seg = Image::scratch (H); - - // For every voxel, stores those polygons that may intersect the voxel - typedef std::map< Vox, std::vector > Vox2Poly; - Vox2Poly voxel2poly; - - // Map each polygon to the underlying voxels - for (size_t poly_index = 0; poly_index != num_polygons(); ++poly_index) { - - const size_t num_vertices = (poly_index < triangles.size()) ? 3 : 4; - - // Figure out the voxel extent of this polygon in three dimensions - Vox lower_bound (H.size(0)-1, H.size(1)-1, H.size(2)-1), upper_bound (0, 0, 0); - VertexList this_poly_verts; - if (num_vertices == 3) - load_triangle_vertices (this_poly_verts, poly_index); - else - load_quad_vertices (this_poly_verts, poly_index - triangles.size()); - for (VertexList::const_iterator v = this_poly_verts.begin(); v != this_poly_verts.end(); ++v) { - for (size_t axis = 0; axis != 3; ++axis) { - const int this_axis_voxel = std::round((*v)[axis]); - lower_bound[axis] = std::min (lower_bound[axis], this_axis_voxel); - upper_bound[axis] = std::max (upper_bound[axis], this_axis_voxel); - } - } - - // Constrain to lie within the dimensions of the image - for (size_t axis = 0; axis != 3; ++axis) { - lower_bound[axis] = std::max(0, lower_bound[axis]); - upper_bound[axis] = std::min(int(H.size(axis)-1), upper_bound[axis]); - } - - // For all voxels within this rectangular region, assign this polygon to the map - Vox voxel; - for (voxel[2] = lower_bound[2]; voxel[2] <= upper_bound[2]; ++voxel[2]) { - for (voxel[1] = lower_bound[1]; voxel[1] <= upper_bound[1]; ++voxel[1]) { - for (voxel[0] = lower_bound[0]; voxel[0] <= upper_bound[0]; ++voxel[0]) { - std::vector this_voxel_polys; -//#if __clang__ -// Vox2Poly::const_iterator existing = voxel2poly.find (voxel); -//#else -// Vox2Poly::iterator existing = voxel2poly.find (voxel); -//#endif - Vox2Poly::const_iterator existing = voxel2poly.find (voxel); - if (existing != voxel2poly.end()) { - this_voxel_polys = existing->second; - voxel2poly.erase (existing); - } else { - // Only call this once each voxel, regardless of the number of intersecting polygons - assign_pos_of (voxel).to (init_seg); - init_seg.value() = ON_MESH; - } - this_voxel_polys.push_back (poly_index); - voxel2poly.insert (std::make_pair (voxel, this_voxel_polys)); - } } } - - } - ++progress; - - - // Find all voxels that are not partial-volumed with the mesh, and are not inside the mesh - // Use a corner of the image FoV to commence filling of the volume, and then check that all - // eight corners have been flagged as outside the volume - const Vox corner_voxels[8] = { - Vox ( 0, 0, 0), - Vox ( 0, 0, H.size (2) - 1), - Vox ( 0, H.size (1) - 1, 0), - Vox ( 0, H.size (1) - 1, H.size (2) - 1), - Vox (H.size (0) - 1, 0, 0), - Vox (H.size (0) - 1, 0, H.size (2) - 1), - Vox (H.size (0) - 1, H.size (1) - 1, 0), - Vox (H.size (0) - 1, H.size (1) - 1, H.size (2) - 1)}; - - // TODO This is slow; is there a faster implementation? - // This is essentially a connected-component analysis... - std::vector to_expand; - to_expand.push_back (corner_voxels[0]); - assign_pos_of (corner_voxels[0]).to (init_seg); - init_seg.value() = vox_mesh_t::OUTSIDE; - do { - const Vox centre_voxel (to_expand.back()); - to_expand.pop_back(); - for (size_t adj_vox_idx = 0; adj_vox_idx != 6; ++adj_vox_idx) { - const Vox this_voxel (centre_voxel + adj_voxels[adj_vox_idx]); - assign_pos_of (this_voxel).to (init_seg); - if (!is_out_of_bounds (init_seg) && init_seg.value() == vox_mesh_t (UNDEFINED)) { - init_seg.value() = vox_mesh_t (OUTSIDE); - to_expand.push_back (this_voxel); - } - } - } while (!to_expand.empty()); - ++progress; - - for (size_t cnr_idx = 0; cnr_idx != 8; ++cnr_idx) { - assign_pos_of (corner_voxels[cnr_idx]).to (init_seg); - if (init_seg.value() == vox_mesh_t (UNDEFINED)) - throw Exception ("Mesh is not bound within image field of view"); - } - - - // Find those voxels that remain unassigned, and set them to INSIDE - for (auto l = Loop (init_seg) (init_seg); l; ++l) { - if (init_seg.value() == vox_mesh_t (UNDEFINED)) - init_seg.value() = vox_mesh_t (INSIDE); - } - ++progress; - - - // Generate the initial estimated PVE image - auto pve_est = Image::scratch (H); - - for (auto l = Loop (init_seg) (init_seg, pve_est); l; ++l) { - switch (init_seg.value()) { - case vox_mesh_t (UNDEFINED): throw Exception ("Code error: poor filling of initial mesh estimate"); break; - case vox_mesh_t (ON_MESH): pve_est.value() = 0.5; break; - case vox_mesh_t (OUTSIDE): pve_est.value() = 0.0; break; - case vox_mesh_t (INSIDE): pve_est.value() = 1.0; break; - } - } - ++progress; - - - // Get better partial volume estimates for all necessary voxels - // TODO This could be multi-threaded, but hard to justify the dev time - static const size_t pve_os_ratio = 10; - - for (Vox2Poly::const_iterator i = voxel2poly.begin(); i != voxel2poly.end(); ++i) { - - const Vox& voxel (i->first); - - // Generate a set of points within this voxel that need to be tested individually - std::vector to_test; - to_test.reserve (Math::pow3 (pve_os_ratio)); - for (size_t x_idx = 0; x_idx != pve_os_ratio; ++x_idx) { - const float x = voxel[0] - 0.5 + ((float(x_idx) + 0.5) / float(pve_os_ratio)); - for (size_t y_idx = 0; y_idx != pve_os_ratio; ++y_idx) { - const float y = voxel[1] - 0.5 + ((float(y_idx) + 0.5) / float(pve_os_ratio)); - for (size_t z_idx = 0; z_idx != pve_os_ratio; ++z_idx) { - const float z = voxel[2] - 0.5 + ((float(z_idx) + 0.5) / float(pve_os_ratio)); - to_test.push_back (Vertex (x, y, z)); - } - } - } - - // Count the number of these points that lie inside the mesh - int inside_mesh_count = 0; - for (std::vector::const_iterator i_p = to_test.begin(); i_p != to_test.end(); ++i_p) { - const Vertex& p (*i_p); - - float best_min_edge_distance = -INFINITY; - bool best_result_inside = false; - - // Only test against those polygons that are near this voxel - for (std::vector::const_iterator polygon_index = i->second.begin(); polygon_index != i->second.end(); ++polygon_index) { - const Eigen::Vector3& n (polygon_normals[*polygon_index]); - - const size_t polygon_num_vertices = (*polygon_index < triangles.size()) ? 3 : 4; - VertexList v; - - bool is_inside = false; - float min_edge_distance = std::numeric_limits::infinity(); - - if (polygon_num_vertices == 3) { - - load_triangle_vertices (v, *polygon_index); - - // First: is it aligned with the normal? - const Vertex poly_centre ((v[0] + v[1] + v[2]) * (1.0 / 3.0)); - const Vertex diff (p - poly_centre); - is_inside = (diff.dot (n) <= 0.0); - - // Second: how well does it project onto this polygon? - const Vertex p_on_plane (p - (n * (diff.dot (n)))); - - std::array edge_distances; - Vertex zero = (v[2]-v[0]).cross (n); zero.normalize(); - Vertex one = (v[1]-v[2]).cross (n); one .normalize(); - Vertex two = (v[0]-v[1]).cross (n); two .normalize(); - edge_distances[0] = (p_on_plane - v[0]).dot (zero); - edge_distances[1] = (p_on_plane - v[2]).dot (one); - edge_distances[2] = (p_on_plane - v[1]).dot (two); - min_edge_distance = std::min (edge_distances[0], std::min (edge_distances[1], edge_distances[2])); - - } else { - - load_quad_vertices (v, *polygon_index); - - // This may be slightly ill-posed with a quad; no guarantee of fixed normal - // Proceed regardless - - // First: is it aligned with the normal? - const Vertex poly_centre ((v[0] + v[1] + v[2] + v[3]) * 0.25f); - const Vertex diff (p - poly_centre); - is_inside = (diff.dot (n) <= 0.0); - - // Second: how well does it project onto this polygon? - const Vertex p_on_plane (p - (n * (diff.dot (n)))); - - for (int edge = 0; edge != 4; ++edge) { - // Want an appropriate vector emanating from this edge from which to test the 'on-plane' distance - // (bearing in mind that there may not be a uniform normal) - // For this, I'm going to take a weighted average based on the relative distance between the - // two points at either end of this edge - // Edge is between points p1 and p2; edge 0 is between points 0 and 1 - const Vertex& p0 ((edge-1) >= 0 ? v[edge-1] : v[3]); - const Vertex& p1 (v[edge]); - const Vertex& p2 ((edge+1) < 4 ? v[edge+1] : v[0]); - const Vertex& p3 ((edge+2) < 4 ? v[edge+2] : v[edge-2]); - - const float d1 = (p1 - p_on_plane).norm(); - const float d2 = (p2 - p_on_plane).norm(); - // Give more weight to the normal at the point that's closer - Vertex edge_normal = (d2*(p0-p1) + d1*(p3-p2)); - edge_normal.normalize(); - - // Now, how far away is the point within the plane from this edge? - const float this_edge_distance = (p_on_plane - p1).dot (edge_normal); - min_edge_distance = std::min (min_edge_distance, this_edge_distance); - - } - - } - - if (min_edge_distance > best_min_edge_distance) { - best_min_edge_distance = min_edge_distance; - best_result_inside = is_inside; - } - - } - - if (best_result_inside) - ++inside_mesh_count; - - } - - assign_pos_of (voxel).to (pve_est); - pve_est.value() = (float)inside_mesh_count / (float)Math::pow3 (pve_os_ratio); - - } - ++progress; - - // Write image to file - auto out = Image::create (path, H); - copy (pve_est, out); - ++progress; - - // Restore the vertex data back to realspace - vertices = vertices_realspace; - - } - - - - void Mesh::smooth (const float spatial_factor, const float influence_factor) - { - if (!vertices.size()) return; - if (quads.size()) - throw Exception ("For now, mesh smoothing is only supported for triangular meshes"); - if (vertices.size() == 3 * vertices.size()) - throw Exception ("Cannot perform smoothing on this mesh: no triangulation information"); - - // Pre-compute polygon centroids and areas - VertexList centroids; - std::vector areas; - for (TriangleList::const_iterator p = triangles.begin(); p != triangles.end(); ++p) { - centroids.push_back ((vertices[(*p)[0]] + vertices[(*p)[1]] + vertices[(*p)[2]]) * (1.0f/3.0f)); - areas.push_back (calc_area (*p)); - } - for (QuadList::const_iterator p = quads.begin(); p != quads.end(); ++p) { - centroids.push_back ((vertices[(*p)[0]] + vertices[(*p)[1]] + vertices[(*p)[2]] + vertices[(*p)[3]]) * 0.25f); - areas.push_back (calc_area (*p)); - } - - // Perform pre-calculation of an appropriate mesh neighbourhood for each vertex - // Use knowledge of the connections between vertices provided by the triangles/quads to - // perform an iterative search outward from each vertex, selecting a subset of - // polygons for each vertex - // Extent of window should be approximately the value of spatial_factor, though only an - // approximate windowing is likely to be used (i.e. number of iterations) - // - // Initialisation is different to iterations: Need a single pass to find those - // polygons that actually use the vertex - std::vector< std::set > vert_polys (vertices.size(), std::set()); - // For each vertex, don't just want to store the polygons within the neighbourhood; - // also want to store those that will be expanded from in the next iteration - std::vector< std::vector > vert_polys_to_expand (vertices.size(), std::vector()); - - for (uint32_t t = 0; t != triangles.size(); ++t) { - for (uint32_t i = 0; i != 3; ++i) { - vert_polys[(triangles[t])[i]].insert (t); - vert_polys_to_expand[(triangles[t])[i]].push_back (t); - } - } - - // Now, we want to expand this selection outwards for each vertex - // To do this, also want to produce a list for each polygon: containing those polygons - // that share a common edge (i.e. two vertices) - std::vector< std::vector > poly_neighbours (triangles.size(), std::vector()); - for (uint32_t i = 0; i != triangles.size(); ++i) { - for (uint32_t j = i+1; j != triangles.size(); ++j) { - if (triangles[i].shares_edge (triangles[j])) { - poly_neighbours[i].push_back (j); - poly_neighbours[j].push_back (i); - - } - } - } - - // TODO Will want to develop a better heuristic for this - for (size_t iter = 0; iter != 8; ++iter) { - for (uint32_t v = 0; v != vertices.size(); ++v) { - - // Find polygons at the outer edge of this expanding front, and add them to the neighbourhood for this vertex - std::vector next_front; - for (std::vector::const_iterator front = vert_polys_to_expand[v].begin(); front != vert_polys_to_expand[v].end(); ++front) { - for (std::vector::const_iterator expansion = poly_neighbours[*front].begin(); expansion != poly_neighbours[*front].end(); ++expansion) { - const std::set::const_iterator existing = vert_polys[v].find (*expansion); - if (existing == vert_polys[v].end()) { - vert_polys[v].insert (*expansion); - next_front.push_back (*expansion); - } - } - } - vert_polys_to_expand[v] = std::move (next_front); - - } - } - - - - // Need to perform a first mollification pass, where the polygon normals are - // smoothed but the vertices are not perturbed - // However, in order to calculate these new normals, we need to calculate new vertex positions! - // Make a copy of the original vertices - const VertexList orig_vertices (vertices); - // Use half standard spatial factor for mollification - // Denominator = 2(SF/2)^2 - const float spatial_mollification_power_multiplier = -2.0f / Math::pow2 (spatial_factor); - // No need to normalise the Gaussian; have to explicitly normalise afterwards - for (uint32_t v = 0; v != vertices.size(); ++v) { - - Vertex new_pos (0.0f, 0.0f, 0.0f); - float sum_weights = 0.0f; - - // For now, just use every polygon as part of the estimate - // Eventually, restrict this to some form of mesh neighbourhood - //for (size_t i = 0; i != centroids.size(); ++i) { - for (std::set::const_iterator it = vert_polys[v].begin(); it != vert_polys[v].end(); ++it) { - const uint32_t i = *it; - float this_weight = areas[i]; - const float distance_sq = (centroids[i] - vertices[v]).squaredNorm(); - this_weight *= std::exp (distance_sq * spatial_mollification_power_multiplier); - const Vertex prediction = centroids[i]; - new_pos += this_weight * prediction; - sum_weights += this_weight; - } - - new_pos *= (1.0f / sum_weights); - vertices[v] = new_pos; - - } - - // Have new vertices; compute polygon normals based on these vertices - VertexList tangents; - for (TriangleList::const_iterator p = triangles.begin(); p != triangles.end(); ++p) - tangents.push_back (calc_normal (*p)); - for (QuadList::const_iterator p = quads.begin(); p != quads.end(); ++p) - tangents.push_back (calc_normal (*p)); - - // Restore the original vertices - vertices = orig_vertices; - - // Now perform the actual smoothing - const float spatial_power_multiplier = -0.5f / Math::pow2 (spatial_factor); - const float influence_power_multiplier = -0.5f / Math::pow2 (influence_factor); - for (size_t v = 0; v != vertices.size(); ++v) { - - Vertex new_pos (0.0f, 0.0f, 0.0f); - float sum_weights = 0.0f; - - //for (size_t i = 0; i != centroids.size(); ++i) { - for (std::set::const_iterator it = vert_polys[v].begin(); it != vert_polys[v].end(); ++it) { - const uint32_t i = *it; - float this_weight = areas[i]; - const float distance_sq = (centroids[i] - vertices[v]).squaredNorm(); - this_weight *= std::exp (distance_sq * spatial_power_multiplier); - const float prediction_distance = (centroids[i] - vertices[v]).dot (tangents[i]); - const Vertex prediction = vertices[v] + (tangents[i] * prediction_distance); - this_weight *= std::exp (Math::pow2 (prediction_distance) * influence_power_multiplier); - new_pos += this_weight * prediction; - sum_weights += this_weight; - } - - new_pos *= (1.0f / sum_weights); - vertices[v] = new_pos; - - } - - // If the vertex normals were calculated previously, re-calculate them - if (normals.size()) - calculate_normals(); - - } - - - - void Mesh::calculate_normals() { normals.clear(); - normals.assign (vertices.size(), Vertex (0.0f, 0.0f, 0.0f)); + normals.assign (vertices.size(), Vertex (0.0, 0.0, 0.0)); for (TriangleList::const_iterator p = triangles.begin(); p != triangles.end(); ++p) { - const Vertex this_normal = calc_normal (*p); + const Vertex this_normal = normal (*this, *p); for (size_t index = 0; index != 3; ++index) normals[(*p)[index]] += this_normal; } for (QuadList::const_iterator p = quads.begin(); p != quads.end(); ++p) { - const Vertex this_normal = calc_normal (*p); + const Vertex this_normal = normal (*this, *p); for (size_t index = 0; index != 4; ++index) normals[(*p)[index]] += this_normal; } @@ -769,8 +279,9 @@ namespace MR in.read (reinterpret_cast(&attribute_byte_count), sizeof(uint16_t)); if (attribute_byte_count) warn_attribute = true; + triangles.push_back ( std::vector { uint32_t(vertices.size()-3), uint32_t(vertices.size()-2), uint32_t(vertices.size()-1) } ); - const Eigen::Vector3 computed_normal = calc_normal (triangles.back()); + const Eigen::Vector3 computed_normal = Surface::normal (*this, triangles.back()); if (computed_normal.dot (normal.cast()) < 0.0) warn_right_hand_rule = true; if (std::abs (computed_normal.dot (normal.cast())) < 0.99) @@ -834,7 +345,7 @@ namespace MR throw Exception ("Error parsing STL file " + Path::basename (path) + ": facet ended with " + str(vertex_index) + " vertices"); triangles.push_back ( std::vector { uint32_t(vertices.size()-3), uint32_t(vertices.size()-2), uint32_t(vertices.size()-1) } ); vertex_index = 0; - const Eigen::Vector3 computed_normal = calc_normal (triangles.back()); + const Eigen::Vector3 computed_normal = Surface::normal (*this, triangles.back()); if (computed_normal.dot (normal) < 0.0) warn_right_hand_rule = true; if (std::abs (computed_normal.dot (normal)) < 0.99) @@ -1081,7 +592,7 @@ namespace MR out.write (reinterpret_cast(&count), sizeof(uint32_t)); const uint16_t attribute_byte_count = 0; for (TriangleList::const_iterator i = triangles.begin(); i != triangles.end(); ++i) { - const Eigen::Vector3 n (calc_normal (*i)); + const Eigen::Vector3 n (normal (*this, *i)); const float n_temp[3] { float(n[0]), float(n[1]), float(n[2]) }; out.write (reinterpret_cast(&n_temp[0]), 3 * sizeof(float)); for (size_t v = 0; v != 3; ++v) { @@ -1098,7 +609,7 @@ namespace MR File::OFStream out (path); out << "solid \n"; for (TriangleList::const_iterator i = triangles.begin(); i != triangles.end(); ++i) { - const Eigen::Vector3 n (calc_normal (*i)); + const Eigen::Vector3 n (normal (*this, *i)); out << "facet normal " << str (n[0]) << " " << str (n[1]) << " " << str (n[2]) << "\n"; out << " outer loop\n"; for (size_t v = 0; v != 3; ++v) { @@ -1131,26 +642,6 @@ namespace MR - - - void Mesh::verify_data() const - { - for (VertexList::const_iterator i = vertices.begin(); i != vertices.end(); ++i) { - if (std::isnan ((*i)[0]) || std::isnan ((*i)[1]) || std::isnan ((*i)[2])) - throw Exception ("NaN values in mesh vertex data"); - } - for (TriangleList::const_iterator i = triangles.begin(); i != triangles.end(); ++i) - for (size_t j = 0; j != 3; ++j) - if ((*i)[j] >= vertices.size()) - throw Exception ("Mesh vertex index exceeds number of vertices read"); - for (QuadList::const_iterator i = quads.begin(); i != quads.end(); ++i) - for (size_t j = 0; j != 4; ++j) - if ((*i)[j] >= vertices.size()) - throw Exception ("Mesh vertex index exceeds number of vertices read"); - } - - - void Mesh::load_triangle_vertices (VertexList& output, const size_t index) const { output.clear(); @@ -1167,40 +658,24 @@ namespace MR - - - Eigen::Vector3 Mesh::calc_normal (const Triangle& in) const - { - Eigen::Vector3 result = (vertices[in[1]] - vertices[in[0]]).cross (vertices[in[2]] - vertices[in[1]]); - result.normalize(); - return result; - } - - Eigen::Vector3 Mesh::calc_normal (const Quad& in) const + void Mesh::verify_data() const { - Eigen::Vector3 norm1 = (vertices[in[1]] - vertices[in[0]]).cross (vertices[in[2]] - vertices[in[1]]); - Eigen::Vector3 norm2 = (vertices[in[2]] - vertices[in[0]]).cross (vertices[in[3]] - vertices[in[2]]); - norm1.normalize(); norm2.normalize(); - Eigen::Vector3 result = norm1 + norm2; - result.normalize(); - return result; + for (VertexList::const_iterator i = vertices.begin(); i != vertices.end(); ++i) { + if (std::isnan ((*i)[0]) || std::isnan ((*i)[1]) || std::isnan ((*i)[2])) + throw Exception ("NaN values in mesh vertex data"); + } + for (TriangleList::const_iterator i = triangles.begin(); i != triangles.end(); ++i) + for (size_t j = 0; j != 3; ++j) + if ((*i)[j] >= vertices.size()) + throw Exception ("Mesh vertex index exceeds number of vertices read"); + for (QuadList::const_iterator i = quads.begin(); i != quads.end(); ++i) + for (size_t j = 0; j != 4; ++j) + if ((*i)[j] >= vertices.size()) + throw Exception ("Mesh vertex index exceeds number of vertices read"); } - float Mesh::calc_area (const Triangle& in) const - { - return 0.5 * ((vertices[in[1]] - vertices[in[0]]).cross (vertices[in[2]] - vertices[in[0]]).norm()); - } - float Mesh::calc_area (const Quad& in) const - { - const std::vector v_one { in[0], in[1], in[2] }; - const std::vector v_two { in[0], in[2], in[3] }; - const Triangle one (v_one), two (v_two); - return (calc_area (one) + calc_area (two)); - } - - } } diff --git a/src/surface/mesh.h b/src/surface/mesh.h index b5f32fcc6a..89e0852a75 100644 --- a/src/surface/mesh.h +++ b/src/surface/mesh.h @@ -37,6 +37,11 @@ namespace MR namespace Surface { + namespace Filter + { + class Smooth; + } + class Mesh { @@ -62,6 +67,14 @@ namespace MR return *this; } + Mesh& operator= (const Mesh& that) { + vertices = that.vertices; + normals = that.normals; + triangles = that.triangles; + quads = that.quads; + return *this; + } + void load (VertexList&& v, TriangleList&& p) { vertices = std::move (v); @@ -102,17 +115,28 @@ namespace MR quads = q; } + void load (VertexList&& v, VertexList&& n, TriangleList&& p, QuadList&& q) { + vertices = std::move (v); + normals = std::move (n); + triangles = std::move (p); + quads = std::move (q); + } + void load (const VertexList& v, const VertexList& n, const TriangleList& p, const QuadList& q) { + vertices = v; + normals = n; + triangles = p; + quads = q; + } - void transform_first_to_realspace (const Header&); - void transform_realspace_to_first (const Header&); - void transform_voxel_to_realspace (const Header&); - void transform_realspace_to_voxel (const Header&); - - void save (const std::string&, const bool binary = false) const; + void clear() { + vertices.clear(); + normals.clear(); + triangles.clear(); + quads.clear(); + } - void output_pve_image (const Header&, const std::string&); - void smooth (const float, const float); + void save (const std::string&, const bool binary = false) const; size_t num_vertices() const { return vertices.size(); } size_t num_triangles() const { return triangles.size(); } @@ -130,6 +154,14 @@ namespace MR const Triangle& tri (const size_t i) const { assert (i < triangles.size()); return triangles[i]; } const Quad& quad (const size_t i) const { assert (i < quads .size()); return quads[i]; } + const VertexList& get_vertices() const { return vertices; } + const VertexList& get_normals() const { return normals; } + const TriangleList& get_triangles() const { return triangles; } + const QuadList& get_quads() const { return quads; } + + void load_triangle_vertices (VertexList&, const size_t) const; + void load_quad_vertices (VertexList&, const size_t) const; + protected: VertexList vertices; @@ -150,16 +182,10 @@ namespace MR void verify_data() const; - void load_triangle_vertices (VertexList&, const size_t) const; - void load_quad_vertices (VertexList&, const size_t) const; - - Eigen::Vector3 calc_normal (const Triangle&) const; - Eigen::Vector3 calc_normal (const Quad&) const; - - float calc_area (const Triangle&) const; - float calc_area (const Quad&) const; - friend class MeshMulti; + friend class Filter::Smooth; + template + void mesh2image (const ImageType&, Mesh&, const default_type); }; diff --git a/src/surface/utils.h b/src/surface/utils.h new file mode 100644 index 0000000000..def68ef3d5 --- /dev/null +++ b/src/surface/utils.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + +#ifndef __surface_utils_h__ +#define __surface_utils_h__ + +#include "surface/mesh.h" +#include "surface/polygon.h" +#include "surface/types.h" + + +namespace MR +{ + namespace Surface + { + + + + inline Vertex normal (const Vertex& one, const Vertex& two, const Vertex& three) + { + return (two - one).cross (three - two).normalized(); + } + inline Vertex normal (const Mesh& mesh, const Triangle& tri) + { + return normal (mesh.vert(tri[0]), mesh.vert(tri[1]), mesh.vert(tri[2])); + } + + inline Vertex normal (const Vertex& one, const Vertex& two, const Vertex& three, const Vertex& four) + { + return (normal (one, two, three) + normal (one, three, four)).normalized(); + } + + inline Vertex normal (const Mesh& mesh, const Quad& quad) + { + return normal (mesh.vert(quad[0]), mesh.vert(quad[1]), mesh.vert(quad[2]), mesh.vert(quad[3])); + } + + + + inline default_type area (const Vertex& one, const Vertex& two, const Vertex& three) + { + return 0.5 * ((two - one).cross (three - two).norm()); + } + + inline default_type area (const Mesh& mesh, const Triangle& tri) + { + return area (mesh.vert(tri[0]), mesh.vert(tri[1]), mesh.vert(tri[2])); + } + + inline default_type area (const Vertex& one, const Vertex& two, const Vertex& three, const Vertex& four) + { + return area (one, two, three) + area (one, three, four); + } + + inline default_type area (const Mesh& mesh, const Quad& quad) + { + return area (mesh.vert(quad[0]), mesh.vert(quad[1]), mesh.vert(quad[2]), mesh.vert(quad[3])); + } + + + + } +} + +#endif + diff --git a/testing/tests/meshconvert b/testing/tests/meshconvert index e4f81d4918..2a762d25bf 100644 --- a/testing/tests/meshconvert +++ b/testing/tests/meshconvert @@ -4,7 +4,7 @@ meshconvert meshconvert/in.vtk tmp.obj -force && testing_diff_mesh tmp.obj meshc meshconvert meshconvert/in.vtk tmp.obj -binary -force && testing_diff_mesh tmp.obj meshconvert/in.vtk 0.001 meshconvert meshconvert/in.vtk tmp.stl -force && testing_diff_mesh tmp.stl meshconvert/in.vtk 0.001 meshconvert meshconvert/in.vtk tmp.stl -binary -force && testing_diff_mesh tmp.stl meshconvert/in.vtk 0.001 -meshconvert meshconvert/in.vtk tmp.vtk -transform_real2first meshconvert/image.mif.gz -force && testing_diff_mesh tmp.vtk meshconvert/first.vtk 0.001 -meshconvert meshconvert/first.vtk tmp.vtk -transform_first2real meshconvert/image.mif.gz -force && testing_diff_mesh tmp.vtk meshconvert/in.vtk 0.001 -meshconvert meshconvert/in.vtk tmp.vtk -transform_real2voxel meshconvert/image.mif.gz -force && testing_diff_mesh tmp.vtk meshconvert/voxel.vtk 0.001 -meshconvert meshconvert/voxel.vtk tmp.vtk -transform_voxel2real meshconvert/image.mif.gz -force && testing_diff_mesh tmp.vtk meshconvert/in.vtk 0.001 +meshconvert meshconvert/in.vtk tmp.vtk -transform real2first meshconvert/image.mif.gz -force && testing_diff_mesh tmp.vtk meshconvert/first.vtk 0.001 +meshconvert meshconvert/first.vtk tmp.vtk -transform first2real meshconvert/image.mif.gz -force && testing_diff_mesh tmp.vtk meshconvert/in.vtk 0.001 +meshconvert meshconvert/in.vtk tmp.vtk -transform real2voxel meshconvert/image.mif.gz -force && testing_diff_mesh tmp.vtk meshconvert/voxel.vtk 0.001 +meshconvert meshconvert/voxel.vtk tmp.vtk -transform voxel2real meshconvert/image.mif.gz -force && testing_diff_mesh tmp.vtk meshconvert/in.vtk 0.001 From fb3a95d0a8f173495c2b59d34d2474e6b963e3f5 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sun, 26 Jun 2016 21:21:47 +1000 Subject: [PATCH 009/723] Enable import of FreeSUrfer surface files --- cmd/meshconvert.cpp | 8 +++- src/surface/freesurfer.h | 61 +++++++++++++++++++++++++++++ src/surface/mesh.cpp | 83 +++++++++++++++++++++++++++++++++++++--- src/surface/mesh.h | 1 + 4 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 src/surface/freesurfer.h diff --git a/cmd/meshconvert.cpp b/cmd/meshconvert.cpp index 17227fb6a5..57c2390b35 100644 --- a/cmd/meshconvert.cpp +++ b/cmd/meshconvert.cpp @@ -65,8 +65,12 @@ void run () try { MR::Surface::Mesh mesh (argument[0]); meshes.push_back (mesh); - } catch (...) { - meshes.load (argument[0]); + } catch (Exception& e) { + try { + meshes.load (argument[0]); + } catch (...) { + throw e; + } } auto opt = get_options ("transform"); diff --git a/src/surface/freesurfer.h b/src/surface/freesurfer.h new file mode 100644 index 0000000000..cca3839caf --- /dev/null +++ b/src/surface/freesurfer.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + +#ifndef __surface_freesurfer_h__ +#define __surface_freesurfer_h__ + +#include + +#include "raw.h" + +namespace MR +{ + namespace Surface + { + namespace FreeSurfer + { + + + constexpr int32_t triangle_file_magic_number = 16777214; + constexpr int32_t quad_file_magic_number = 16777215; + + + inline int32_t get_int24_BE (std::ifstream& stream) + { + uint8_t bytes[3]; + for (size_t i = 0; i != 3; ++i) + stream.read (reinterpret_cast(bytes+i), 1); + return (int32_t(bytes[0]) << 16) | (int32_t(bytes[1]) << 8) | int32_t(bytes[2]); + } + + + + template + inline T get_BE (std::ifstream& stream) + { + T temp; + stream.read (reinterpret_cast(&temp), sizeof(T)); + return Raw::fetch_BE (&temp); + } + + + + } + } +} + +#endif + diff --git a/src/surface/mesh.cpp b/src/surface/mesh.cpp index e468b6f739..51c5c3b0d2 100644 --- a/src/surface/mesh.cpp +++ b/src/surface/mesh.cpp @@ -18,9 +18,10 @@ #include #include - +#include #include +#include "surface/freesurfer.h" #include "surface/utils.h" @@ -33,14 +34,20 @@ namespace MR Mesh::Mesh (const std::string& path) { - if (path.substr (path.size() - 4) == ".vtk" || path.substr (path.size() - 4) == ".VTK") + if (path.substr (path.size() - 4) == ".vtk" || path.substr (path.size() - 4) == ".VTK") { load_vtk (path); - else if (path.substr (path.size() - 4) == ".stl" || path.substr (path.size() - 4) == ".STL") + } else if (path.substr (path.size() - 4) == ".stl" || path.substr (path.size() - 4) == ".STL") { load_stl (path); - else if (path.substr (path.size() - 4) == ".obj" || path.substr (path.size() - 4) == ".OBJ") + } else if (path.substr (path.size() - 4) == ".obj" || path.substr (path.size() - 4) == ".OBJ") { load_obj (path); - else - throw Exception ("Input mesh file not in supported format"); + } else { + try { + load_fs (path); + } catch (...) { + clear(); + throw Exception ("Input mesh file not in supported format"); + } + } name = path; } @@ -378,6 +385,8 @@ namespace MR verify_data(); } + + void Mesh::load_obj (const std::string& path) { @@ -489,6 +498,68 @@ namespace MR } + void Mesh::load_fs (const std::string& path) + { + + std::ifstream in (path.c_str(), std::ios_base::in | std::ios_base::binary); + if (!in) + throw Exception ("Error opening input file!"); + + const int32_t magic_number = FreeSurfer::get_int24_BE (in); + + if (magic_number == FreeSurfer::triangle_file_magic_number) { + + char c; + for (size_t i = 0; i != 2; ++i) { + do { + in.read (&c, 1); + } while (c != '\n'); + } + const int32_t num_vertices = FreeSurfer::get_BE (in); + const int32_t num_polygons = FreeSurfer::get_BE (in); + vertices.reserve (num_vertices); + for (int32_t i = 0; i != num_vertices; ++i) { + float temp[3]; + for (size_t axis = 0; axis != 3; ++axis) + temp[axis] = FreeSurfer::get_BE (in); + vertices.push_back (Vertex (temp[0], temp[1], temp[2])); + } + if (!in.good()) + throw Exception ("Error reading FreeSurfer file: EOF reached"); + for (int32_t i = 0; i != num_polygons; ++i) { + int32_t temp[3]; + for (size_t v = 0; v != 3; ++v) + temp[v] = FreeSurfer::get_BE (in); + triangles.push_back (Triangle (temp)); + } + if (!in.good()) + throw Exception ("Error reading FreeSurfer file: EOF reached"); + + } else if (magic_number == FreeSurfer::quad_file_magic_number) { + + const int32_t num_vertices = FreeSurfer::get_int24_BE (in); + const int32_t num_polygons = FreeSurfer::get_int24_BE (in); + vertices.reserve (num_vertices); + for (int32_t i = 0; i != num_vertices; ++i) { + int16_t temp[3]; + for (size_t axis = 0; axis != 3; ++axis) + temp[axis] = FreeSurfer::get_BE (in); + vertices.push_back (Vertex (0.01 * temp[0], 0.01 * temp[1], 0.01 * temp[2])); + } + for (int32_t i = 0; i != num_polygons; ++i) { + int32_t temp[4]; + for (size_t v = 0; v != 4; ++v) + temp[v] = FreeSurfer::get_int24_BE (in); + quads.push_back (Quad (temp)); + } + + } else { + throw Exception ("File " + Path::basename (path) + " is not a FreeSurfer surface file"); + } + + verify_data(); + } + diff --git a/src/surface/mesh.h b/src/surface/mesh.h index 89e0852a75..b435e2b84f 100644 --- a/src/surface/mesh.h +++ b/src/surface/mesh.h @@ -176,6 +176,7 @@ namespace MR void load_vtk (const std::string&); void load_stl (const std::string&); void load_obj (const std::string&); + void load_fs (const std::string&); void save_vtk (const std::string&, const bool) const; void save_stl (const std::string&, const bool) const; void save_obj (const std::string&) const; From 3eaed6cbeec30385155ea0a43ba41e86eb17d396 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sun, 26 Jun 2016 23:53:18 +1000 Subject: [PATCH 010/723] Surface::Algo::image2mesh_blocky(): Fix RH rule of output --- src/surface/algo/image2mesh.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/surface/algo/image2mesh.h b/src/surface/algo/image2mesh.h index 3128e684e8..9ae82d58fe 100644 --- a/src/surface/algo/image2mesh.h +++ b/src/surface/algo/image2mesh.h @@ -50,11 +50,11 @@ namespace MR Vox ( 0, 1, 0), Vox ( 1, 0, 0) }; - static const int plane_axes[6][2] = { {0, 1}, + static const int plane_axes[6][2] = { {1, 0}, {0, 2}, - {1, 2}, + {2, 1}, {0, 1}, - {0, 2}, + {2, 0}, {1, 2} }; if (input_image.ndim() != 3) From 4ef18ee3b2829645569efc5fd0a82dcb91389476 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 27 Jun 2016 16:56:10 +1000 Subject: [PATCH 011/723] New class Surface::Scalar Responsible for handling storage of a quantitative value per vertex on a surface mesh. --- src/surface/freesurfer.h | 2 + src/surface/mesh.cpp | 4 +- src/surface/scalar.cpp | 135 +++++++++++++++++++++++++++++++++++++++ src/surface/scalar.h | 89 ++++++++++++++++++++++++++ 4 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 src/surface/scalar.cpp create mode 100644 src/surface/scalar.h diff --git a/src/surface/freesurfer.h b/src/surface/freesurfer.h index cca3839caf..0612e431cb 100644 --- a/src/surface/freesurfer.h +++ b/src/surface/freesurfer.h @@ -32,6 +32,8 @@ namespace MR constexpr int32_t triangle_file_magic_number = 16777214; constexpr int32_t quad_file_magic_number = 16777215; + constexpr int32_t new_curv_file_magic_number = 16777215; + inline int32_t get_int24_BE (std::ifstream& stream) { diff --git a/src/surface/mesh.cpp b/src/surface/mesh.cpp index 51c5c3b0d2..9445252402 100644 --- a/src/surface/mesh.cpp +++ b/src/surface/mesh.cpp @@ -45,10 +45,10 @@ namespace MR load_fs (path); } catch (...) { clear(); - throw Exception ("Input mesh file not in supported format"); + throw Exception ("Input surface mesh file not in supported format"); } } - name = path; + name = Path::banename (path); } diff --git a/src/surface/scalar.cpp b/src/surface/scalar.cpp new file mode 100644 index 0000000000..3497e09eb7 --- /dev/null +++ b/src/surface/scalar.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + +#include "surface/scalar.h" + +#include "math/math.h" + +#include "surface/freesurfer.h" + + +namespace MR +{ + namespace Surface + { + + + + Scalar::Scalar (const std::string& path, const Mesh& mesh) + { + try { + values = load_vector (path); + } catch (...) { + try { + load_fs_w (path, mesh); + } catch (...) { + try { + load_fs_curv (path, mesh); + } catch (...) { + throw Exception ("Input surface scalar file \"" + path + "\" not in supported format"); + } + } + } + if (size_t(values.size()) != mesh.num_vertices()) + throw Exception ("Input surface scalar file \"" + path + "\" has incorrect number of vertices"); + name = Path::basename (path); + } + + + + void Scalar::save (const std::string& path) const + { + save_vector (values, path); + } + + + + void Scalar::load_fs_w (const std::string& path, const Mesh& mesh) + { + std::ifstream in (path, std::ios_base::in | std::ios_base::binary); + if (!in) + throw Exception ("Error opening surface scalar file \"" + path + "\""); + + FreeSurfer::get_BE (in); // 'latency' + const int32_t num_entries = FreeSurfer::get_int24_BE (in); + values = vector_type::Zero (num_vertices); + for (size_t i = 0; i != num_entries; ++i) { + const int32_t index = FreeSurfer::get_int24_BE (in); + const float value = FreeSurfer::get_BE (in); + if (index >= mesh.num_vertices()) + throw Exception ("Error opening file \"" + path + "\" as FreeSurfer w-file: invalid vertex index"); + if (!in.good()) + throw Exception ("Error opening file \"" + path + "\" as FreeSurfer w-file: truncated file"); + values[index] = value; + } + } + + + + void Scalar::load_fs_curv (const std::string& path, const Mesh& mesh) + { + std::ifstream in (path, std::ios_base::in | std::ios_base::binary); + if (!in) + throw Exception ("Error opening surface scalar file \"" + path + "\""); + + const int32_t magic_number = FreeSurfer::get_int24_BE (in); + if (magic_number == FreeSurfer::new_curv_file_magic_number) { + + const int32_t num_vertices = FreeSurfer::get_BE (in); + if (num_vertices != mesh.num_vertices()) + throw Exception ("Error opening file \"" + path + "\" as Freesurfer curv file: Incorrect number of vertices"); + + const int32_t num_faces = FreeSurfer::get_BE (in); + if (num_faces != mesh.num_polygons()) + throw Exception ("Error opening file \"" + path + "\" as Freesurfer curv file: Incorrect number of polygons"); + + const int32_t vals_per_vertex = FreeSurfer::get_BE (in); + if (vals_per_vertex != 1) + throw Exception ("Error opening file \"" + path + "\" as Freesurfer curv file: Only support 1 value per vertex"); + + values.resize (num_vertices); + for (int32_t i = 0; i != num_vertices; ++i) + values[i] = FreeSurfer::get_BE (in); + + } else { + + const int32_t num_vertices = magic_number; + if (num_vertices != mesh.num_vertices()) + throw Exception ("Error opening file \"" + path + "\" as Freesurfer curv file: Incorrect number of vertices"); + + const int32_t num_faces = FreeSurfer::get_int24_BE (in); + if (num_faces != mesh.num_polygons()) + throw Exception ("Error opening file \"" + path + "\" as Freesurfer curv file: Incorrect number of polygons"); + + values.resize (mesh.num_vertices()); + for (int32_t i = 0; i != num_vertices; ++i) + values[i] = 0.01 * FreeSurfer::get_BE (in); + + } + + if (!in.good()) + throw Exception ("Error opening file \"" + path + "\" as Freesurfer curv file: Truncated file"); + + + } + + + + + } +} + + diff --git a/src/surface/scalar.h b/src/surface/scalar.h new file mode 100644 index 0000000000..81a7f25400 --- /dev/null +++ b/src/surface/scalar.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __surface_scalar_h__ +#define __surface_scalar_h__ + + +#include "surface/mesh.h" + + +namespace MR +{ + namespace Surface + { + + + class Scalar { + + public: + typedef Eigen::Array vector_type; + + Scalar (const std::string&, const Mesh&); + + Scalar (const Scalar& that) = default; + + Scalar (Scalar&& that) : + values (std::move (that.values)) { } + + Scalar() { } + + Scalar& operator= (Scalar&& that) { + values = std::move (that.values); + return *this; + } + + Scalar& operator= (const Scalar& that) { + values = that.values; + return *this; + } + + + void clear() { + values.clear(); + } + + + void save (const std::string&) const; + + size_t num_values() const { return values.size(); } + + const std::string& get_name() const { return name; } + void set_name (const std::string& s) { name = s; } + + default_type value (const size_t i) const { assert (i < values.size()); return values[i]; } + + const vector_type& get_values() const { return values; } + + + protected: + vector_type values; + + + private: + std::string name; + + void load_fs_w (const std::string&, const Mesh&); + void load_fs_curv (const std::string&, const Mesh&); + + }; + + + + } +} + +#endif + From a0b7752706c4b121a0bf1630ecfdd07f5bedcd95 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 29 Jun 2016 10:37:17 +1000 Subject: [PATCH 012/723] amp2sh: Remove redundancy in help page description --- cmd/amp2sh.cpp | 11 ----------- docs/reference/commands/amp2sh.rst | 4 ---- 2 files changed, 15 deletions(-) diff --git a/cmd/amp2sh.cpp b/cmd/amp2sh.cpp index dbf6c1c6ce..2b421924ac 100644 --- a/cmd/amp2sh.cpp +++ b/cmd/amp2sh.cpp @@ -41,17 +41,6 @@ void usage () "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." - + "Note that this program makes use of implied symmetries in the diffusion " - "profile. First, the fact the signal attenuation profile is real implies " - "that it has conjugate symmetry, i.e. Y(l,-m) = Y(l,m)* (where * denotes the " - "complex conjugate). Second, the diffusion profile should be antipodally " - "symmetric (i.e. S(x) = S(-x)), implying that all odd l components should be " - "zero. Therefore, this program only computes the even elements." - - + "Note that the spherical harmonics equations used here differ slightly from " - "those conventionally used, in that the (-1)^m factor has been omitted. This " - "should be taken into account in all subsequent calculations." - + Math::SH::encoding_description; ARGUMENTS diff --git a/docs/reference/commands/amp2sh.rst b/docs/reference/commands/amp2sh.rst index d6b869ffd0..31e2ebb025 100644 --- a/docs/reference/commands/amp2sh.rst +++ b/docs/reference/commands/amp2sh.rst @@ -20,10 +20,6 @@ convert a set of amplitudes (defined along a set of corresponding directions) to The directions can be defined either as a DW gradient scheme (for example to compute the SH representation of the DW signal) or a set of [az el] pairs as output by the dirgen command. 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. -Note that this program makes use of implied symmetries in the diffusion profile. First, the fact the signal attenuation profile is real implies that it has conjugate symmetry, i.e. Y(l,-m) = Y(l,m)* (where * denotes the complex conjugate). Second, the diffusion profile should be antipodally symmetric (i.e. S(x) = S(-x)), implying that all odd l components should be zero. Therefore, this program only computes the even elements. - -Note that the spherical harmonics equations used here differ slightly from those conventionally used, in that the (-1)^m factor has been omitted. This should be taken into account in all subsequent calculations. - The spherical harmonic coefficients are stored as follows. First, since the signal attenuation profile is real, it has conjugate symmetry, i.e. Y(l,-m) = Y(l,m)* (where * denotes the complex conjugate). Second, the diffusion profile should be antipodally symmetric (i.e. S(x) = S(-x)), implying that all odd l components should be zero. Therefore, only the even elements are computed. Note that the spherical harmonics equations used here differ slightly from those conventionally used, in that the (-1)^m factor has been omitted. This should be taken into account in all subsequent calculations. Each volume in the output image corresponds to a different spherical harmonic component. Each volume will correspond to the following: volume 0: l = 0, m = 0 ; volume 1: l = 2, m = -2 (imaginary part of m=2 SH) ; volume 2: l = 2, m = -1 (imaginary part of m=1 SH) ; volume 3: l = 2, m = 0 ; volume 4: l = 2, m = 1 (real part of m=1 SH) ; volume 5: l = 2, m = 2 (real part of m=2 SH) ; etc... Options From 2accdea49901b28097bc769cad007899eca67ed0 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 29 Jun 2016 13:19:37 +1000 Subject: [PATCH 013/723] Stats: Change primary operating type to double Measures of connectivity for smoothing / statistical enhancement will remain as floats to minimise RAM usage, but all critical calculations will now be done using doubles. In addition, a lot of stats code has been moved to .cpp files to reduce compiler burden; the functions involved are relatively computationally expensive and therefore performance should not be affected. Following discussion in #684. --- cmd/fixelcfestats.cpp | 99 ++++++++++---------- cmd/mrclusterstats.cpp | 96 +++++++++---------- lib/math/stats/glm.cpp | 149 ++++++++++++++++++++++++++++++ lib/math/stats/glm.h | 136 ++++++--------------------- lib/math/stats/permutation.cpp | 106 +++++++++++++++++++++ lib/math/stats/permutation.h | 87 ++++------------- lib/math/stats/typedefs.h | 42 +++++++++ src/stats/cfe.cpp | 124 +++++++++++++++++++++++++ src/stats/cfe.h | 104 +++++---------------- src/stats/cluster.cpp | 45 +++++++++ src/stats/cluster.h | 28 +++--- src/stats/permstack.cpp | 50 ++++++++++ src/stats/permstack.h | 61 ++++++++++++ src/stats/permtest.h | 164 +++++++++++++++------------------ src/stats/tfce.cpp | 55 +++++++++++ src/stats/tfce.h | 38 +++----- 16 files changed, 900 insertions(+), 484 deletions(-) create mode 100644 lib/math/stats/glm.cpp create mode 100644 lib/math/stats/permutation.cpp create mode 100644 lib/math/stats/typedefs.h create mode 100644 src/stats/cfe.cpp create mode 100644 src/stats/cluster.cpp create mode 100644 src/stats/permstack.cpp create mode 100644 src/stats/permstack.h create mode 100644 src/stats/tfce.cpp diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index d9d40c8d34..7fcb9d2524 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -22,8 +22,9 @@ #include "sparse/fixel_metric.h" #include "sparse/keys.h" #include "sparse/image.h" -#include "math/stats/permutation.h" #include "math/stats/glm.h" +#include "math/stats/permutation.h" +#include "math/stats/typedefs.h" #include "stats/cfe.h" #include "stats/permtest.h" #include "dwi/tractography/file.h" @@ -36,11 +37,10 @@ using namespace MR; using namespace App; using namespace MR::DWI::Tractography::Mapping; +using namespace MR::Math::Stats; using Sparse::FixelMetric; - -typedef float value_type; -typedef Eigen::Matrix matrix_type; -typedef Eigen::Array vector_type; +using Stats::CFE::direction_type; +using Stats::CFE::connectivity_value_type; #define DEFAULT_PERMUTATIONS 5000 #define DEFAULT_CFE_DH 0.1 @@ -128,7 +128,8 @@ void write_fixel_output (const std::string& filename, const VectorType data, const Header& header, Sparse::Image& mask_vox, - Image& indexer_vox) { + Image& indexer_vox) +{ Sparse::Image output (filename, header); for (auto i = Loop (mask_vox)(mask_vox, indexer_vox, output); i; ++i) { output.value().set_size (mask_vox.value().size()); @@ -148,20 +149,20 @@ void run() { auto opt = get_options ("negative"); bool compute_negative_contrast = opt.size() ? true : false; - value_type cfe_dh = get_option_value ("cfe_dh", DEFAULT_CFE_DH); - value_type cfe_h = get_option_value ("cfe_h", DEFAULT_CFE_H); - value_type cfe_e = get_option_value ("cfe_e", DEFAULT_CFE_E); - value_type cfe_c = get_option_value ("cfe_c", DEFAULT_CFE_C); - int num_perms = get_option_value ("nperms", DEFAULT_PERMUTATIONS); - value_type angular_threshold = get_option_value ("angle", DEFAULT_ANGLE_THRESHOLD); - const float angular_threshold_dp = cos (angular_threshold * (Math::pi/180.0)); + 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 int num_perms = get_option_value ("nperms", DEFAULT_PERMUTATIONS); + const value_type angular_threshold = get_option_value ("angle", DEFAULT_ANGLE_THRESHOLD); + const value_type angular_threshold_dp = cos (angular_threshold * (Math::pi/180.0)); - value_type connectivity_threshold = get_option_value ("connectivity", DEFAULT_CONNECTIVITY_THRESHOLD); - value_type smooth_std_dev = get_option_value ("smooth", DEFAULT_SMOOTHING_STD) / 2.3548; + const value_type connectivity_threshold = get_option_value ("connectivity", DEFAULT_CONNECTIVITY_THRESHOLD); + const value_type smooth_std_dev = get_option_value ("smooth", DEFAULT_SMOOTHING_STD) / 2.3548; - bool do_nonstationary_adjustment = get_options ("nonstationary").size(); + const bool do_nonstationary_adjustment = get_options ("nonstationary").size(); - int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_PERMUTATIONS_NONSTATIONARITY); + const int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_PERMUTATIONS_NONSTATIONARITY); // Read filenames std::vector filenames; @@ -181,12 +182,12 @@ void run() { } // Load design matrix: - Eigen::Matrix design = load_matrix (argument[2]); + const matrix_type design = load_matrix (argument[2]); if (design.rows() != (ssize_t)filenames.size()) throw Exception ("number of input files does not match number of rows in design matrix"); // Load contrast matrix: - Eigen::Matrix contrast = load_matrix (argument[3]); + const matrix_type contrast = load_matrix (argument[3]); if (contrast.cols() != design.cols()) throw Exception ("the number of contrasts does not equal the number of columns in the design matrix"); @@ -205,7 +206,7 @@ void run() { fixel_index_image.value() = -1; std::vector positions; - std::vector directions; + std::vector directions; Transform image_transform (mask_fixel_image); for (auto i = Loop (mask_fixel_image)(mask_fixel_image, fixel_index_image); i; ++i) { @@ -214,23 +215,23 @@ void run() { int32_t fixel_count = 0; for (size_t f = 0; f != mask_fixel_image.value().size(); ++f, ++fixel_count) { directions.push_back (mask_fixel_image.value()[f].dir); - Eigen::Vector3 pos (mask_fixel_image.index(0), mask_fixel_image.index(1), mask_fixel_image.index(2)); + const Eigen::Vector3 pos (mask_fixel_image.index(0), mask_fixel_image.index(1), mask_fixel_image.index(2)); positions.push_back (image_transform.voxel2scanner * pos); } fixel_index_image.index(3) = 1; fixel_index_image.value() = fixel_count; } - uint32_t num_fixels = directions.size(); + const uint32_t num_fixels = directions.size(); CONSOLE ("number of fixels: " + str(num_fixels)); // Compute fixel-fixel connectivity std::vector > connectivity_matrix (num_fixels); std::vector fixel_TDI (num_fixels, 0.0); - std::string track_filename = argument[4]; - std::string output_prefix = argument[5]; + const std::string track_filename = argument[4]; + const std::string output_prefix = argument[5]; 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"]); if (!num_tracks) @@ -255,7 +256,7 @@ void run() { // Normalise connectivity matrix and threshold, pre-compute fixel-fixel weights for smoothing. - std::vector > smoothing_weights (num_fixels); + std::vector > smoothing_weights (num_fixels); bool do_smoothing = false; const value_type gaussian_const2 = 2.0 * smooth_std_dev * smooth_std_dev; value_type gaussian_const1 = 1.0; @@ -268,17 +269,17 @@ void run() { for (unsigned int fixel = 0; fixel < num_fixels; ++fixel) { auto it = connectivity_matrix[fixel].begin(); while (it != connectivity_matrix[fixel].end()) { - value_type connectivity = it->second.value / value_type (fixel_TDI[fixel]); + const value_type connectivity = it->second.value / value_type (fixel_TDI[fixel]); if (connectivity < connectivity_threshold) { connectivity_matrix[fixel].erase (it++); } else { if (do_smoothing) { - value_type distance = std::sqrt (Math::pow2 (positions[fixel][0] - positions[it->first][0]) + - Math::pow2 (positions[fixel][1] - positions[it->first][1]) + - Math::pow2 (positions[fixel][2] - positions[it->first][2])); - value_type smoothing_weight = connectivity * gaussian_const1 * std::exp (-std::pow (distance, 2) / gaussian_const2); - if (smoothing_weight > connectivity_threshold) - smoothing_weights[fixel].insert (std::pair (it->first, smoothing_weight)); + const value_type distance = std::sqrt (Math::pow2 (positions[fixel][0] - positions[it->first][0]) + + Math::pow2 (positions[fixel][1] - positions[it->first][1]) + + Math::pow2 (positions[fixel][2] - positions[it->first][2])); + const Stats::CFE::connectivity smoothing_weight (Stats::CFE::connectivity_value_type(connectivity * gaussian_const1 * std::exp (-std::pow (distance, 2) / gaussian_const2))); + if (smoothing_weight.value > connectivity_threshold) + smoothing_weights[fixel].insert (std::pair (it->first, smoothing_weight)); } // Here we pre-exponentiate each connectivity value by C it->second.value = std::pow (connectivity, cfe_c); @@ -289,7 +290,7 @@ void run() { Stats::CFE::connectivity self_connectivity; self_connectivity.value = 1.0; connectivity_matrix[fixel].insert (std::pair (fixel, self_connectivity)); - smoothing_weights[fixel].insert (std::pair (fixel, gaussian_const1)); + smoothing_weights[fixel].insert (std::pair (fixel, gaussian_const1)); progress++; } } @@ -298,14 +299,14 @@ void run() { for (size_t fixel = 0; fixel < num_fixels; ++fixel) { value_type sum = 0.0; for (auto it = smoothing_weights[fixel].begin(); it != smoothing_weights[fixel].end(); ++it) - sum += it->second; + sum += it->second.value; value_type norm_factor = 1.0 / sum; for (auto it = smoothing_weights[fixel].begin(); it != smoothing_weights[fixel].end(); ++it) - it->second *= norm_factor; + it->second.value *= norm_factor; } // Load input data - Eigen::Matrix data (num_fixels, filenames.size()); + matrix_type data (num_fixels, filenames.size()); { ProgressBar progress ("loading input images", filenames.size()); for (size_t subject = 0; subject < filenames.size(); subject++) { @@ -316,16 +317,16 @@ void run() { for (auto voxel = Loop(fixel)(fixel, fixel_index_image); voxel; ++voxel) { fixel_index_image.index(3) = 0; - int32_t index = fixel_index_image.value(); + const int32_t index = fixel_index_image.value(); fixel_index_image.index(3) = 1; - int32_t number_fixels = fixel_index_image.value(); + const int32_t number_fixels = fixel_index_image.value(); // for each fixel in the mask, find the corresponding fixel in this subject voxel for (int32_t i = index; i < index + number_fixels; ++i) { value_type largest_dp = 0.0; int index_of_closest_fixel = -1; for (size_t f = 0; f != fixel.value().size(); ++f) { - value_type dp = std::abs (directions[i].dot(fixel.value()[f].dir)); + const value_type dp = std::abs (directions[i].dot(fixel.value()[f].dir)); if (dp > largest_dp) { largest_dp = dp; index_of_closest_fixel = f; @@ -339,9 +340,9 @@ void run() { // Smooth the data for (size_t fixel = 0; fixel < num_fixels; ++fixel) { value_type value = 0.0; - std::map::const_iterator it = smoothing_weights[fixel].begin(); + std::map::const_iterator it = smoothing_weights[fixel].begin(); for (; it != smoothing_weights[fixel].end(); ++it) - value += temp_fixel_data[it->first] * it->second; + value += temp_fixel_data[it->first] * it->second.value; data (fixel, subject) = value; } progress++; @@ -358,17 +359,17 @@ void run() { write_fixel_output (output_prefix + "beta" + str(i) + ".msf", temp.row(i), input_header, mask_fixel_image, fixel_index_image); ++progress; } - temp = Math::Stats::GLM::abs_effect_size (data, design, contrast); + temp = Math::Stats::GLM::abs_effect_size (data, design, contrast); ++progress; write_fixel_output (output_prefix + "abs_effect.msf", temp.row(0), input_header, mask_fixel_image, fixel_index_image); ++progress; - temp = Math::Stats::GLM::std_effect_size (data, design, contrast); + temp = Math::Stats::GLM::std_effect_size (data, design, contrast); ++progress; write_fixel_output (output_prefix + "std_effect.msf", temp.row(0), input_header, mask_fixel_image, fixel_index_image); ++progress; - temp = Math::Stats::GLM::stdev (data, design); + temp = Math::Stats::GLM::stdev (data, design); ++progress; write_fixel_output (output_prefix + "std_dev.msf", temp.row(0), input_header, mask_fixel_image, fixel_index_image); } - Math::Stats::GLMTTest glm_ttest (data, design, contrast); + Math::Stats::GLMTTest glm_ttest (data, design, contrast); Stats::CFE::Enhancer cfe_integrator (connectivity_matrix, cfe_dh, cfe_e, cfe_h); - Stats::PermTest::empirical_vector_type empirical_cfe_statistic; + vector_type empirical_cfe_statistic; Header output_header (input_header); output_header.keyval()["num permutations"] = str(num_perms); @@ -425,14 +426,14 @@ void run() { save_matrix (perm_distribution, output_prefix + "perm_dist.txt"); ++progress; vector_type pvalue_output (num_fixels); - Math::Stats::statistic2pvalue (perm_distribution, cfe_output, pvalue_output); ++progress; + Math::Stats::Permutation::statistic2pvalue (perm_distribution, cfe_output, pvalue_output); ++progress; write_fixel_output (output_prefix + "fwe_pvalue.msf", pvalue_output, output_header, mask_fixel_image, fixel_index_image); ++progress; write_fixel_output (output_prefix + "uncorrected_pvalue.msf", uncorrected_pvalues, output_header, mask_fixel_image, fixel_index_image); ++progress; if (compute_negative_contrast) { save_matrix (*perm_distribution_neg, output_prefix + "perm_dist_neg.txt"); ++progress; vector_type pvalue_output_neg (num_fixels); - Math::Stats::statistic2pvalue (*perm_distribution_neg, *cfe_output_neg, pvalue_output_neg); ++progress; + Math::Stats::Permutation::statistic2pvalue (*perm_distribution_neg, *cfe_output_neg, pvalue_output_neg); ++progress; write_fixel_output (output_prefix + "fwe_pvalue_neg.msf", pvalue_output_neg, output_header, mask_fixel_image, fixel_index_image); ++progress; write_fixel_output (output_prefix + "uncorrected_pvalue_neg.msf", *uncorrected_pvalues_neg, output_header, mask_fixel_image, fixel_index_image); } diff --git a/cmd/mrclusterstats.cpp b/cmd/mrclusterstats.cpp index 7d18bf600a..b768b4226a 100644 --- a/cmd/mrclusterstats.cpp +++ b/cmd/mrclusterstats.cpp @@ -21,8 +21,9 @@ #include "math/SH.h" #include "dwi/directions/predefined.h" #include "timer.h" -#include "math/stats/permutation.h" #include "math/stats/glm.h" +#include "math/stats/permutation.h" +#include "math/stats/typedefs.h" #include "stats/tfce.h" #include "stats/cluster.h" #include "stats/permtest.h" @@ -30,6 +31,7 @@ using namespace MR; using namespace App; +using namespace MR::Math::Stats; #define DEFAULT_PERMUTATIONS 5000 @@ -99,22 +101,18 @@ void usage () } -typedef Stats::TFCE::value_type value_type; -typedef Eigen::Matrix matrix_type; -typedef Eigen::Array vector_type; - void run() { - value_type cluster_forming_threshold = get_option_value ("threshold", NAN); - value_type tfce_dh = get_option_value ("tfce_dh", DEFAULT_TFCE_DH); - value_type tfce_H = get_option_value ("tfce_h", DEFAULT_TFCE_H); - value_type tfce_E = get_option_value ("tfce_e", DEFAULT_TFCE_E); - int num_perms = get_option_value ("nperms", DEFAULT_PERMUTATIONS); - int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_PERMUTATIONS_NONSTATIONARITY); + 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 int num_perms = get_option_value ("nperms", DEFAULT_PERMUTATIONS); + const int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_PERMUTATIONS_NONSTATIONARITY); - bool do_26_connectivity = get_options("connectivity").size(); - bool do_nonstationary_adjustment = get_options ("nonstationary").size(); + const bool do_26_connectivity = get_options("connectivity").size(); + const bool do_nonstationary_adjustment = get_options ("nonstationary").size(); // Read filenames std::vector subjects; @@ -127,18 +125,18 @@ void run() { } // Load design matrix: - const matrix_type design = load_matrix (argument[1]); + const matrix_type design = load_matrix (argument[1]); if (design.rows() != (ssize_t)subjects.size()) throw Exception ("number of input files does not match number of rows in design matrix"); // Load contrast matrix: - matrix_type contrast = load_matrix (argument[2]); + const matrix_type contrast = load_matrix (argument[2]); if (contrast.cols() != design.cols()) throw Exception ("the number of contrasts does not equal the number of columns in the design matrix"); // Load Mask auto header = Header::open (argument[3]); - auto mask_image = header.get_image(); + auto mask_image = header.get_image(); Filter::Connector connector (do_26_connectivity); std::vector > mask_indices = connector.precompute_adjacency (mask_image); @@ -146,13 +144,12 @@ void run() { const size_t num_vox = mask_indices.size(); matrix_type data (num_vox, subjects.size()); - { // Load images ProgressBar progress("loading images", subjects.size()); for (size_t subject = 0; subject < subjects.size(); subject++) { LogLevelLatch log_level (0); - auto input_image = Image::open(subjects[subject]).with_direct_io (3); + auto input_image = Image::open (subjects[subject]); //.with_direct_io (3); <- Should be inputting 3D images? check_dimensions (input_image, mask_image, 0, 3); int index = 0; std::vector >::iterator it; @@ -179,7 +176,7 @@ void run() { output_header.keyval()["tfce_h"] = str(tfce_H); } - std::string prefix (argument[4]); + const std::string prefix (argument[4]); std::string cluster_name (prefix); if (std::isfinite (cluster_forming_threshold)) @@ -187,41 +184,41 @@ void run() { else cluster_name.append ("tfce.mif"); - auto cluster_image = Image::create (cluster_name, output_header); - auto tvalue_image = Image::create (prefix + "tvalue.mif", output_header); - auto fwe_pvalue_image = Image::create (prefix + "fwe_pvalue.mif", output_header); - auto uncorrected_pvalue_image = Image::create (prefix + "uncorrected_pvalue.mif", output_header); - Image cluster_image_neg; - Image fwe_pvalue_image_neg; - Image uncorrected_pvalue_image_neg; + auto cluster_image = Image::create (cluster_name, output_header); + auto tvalue_image = Image::create (prefix + "tvalue.mif", output_header); + auto fwe_pvalue_image = Image::create (prefix + "fwe_pvalue.mif", output_header); + auto uncorrected_pvalue_image = Image::create (prefix + "uncorrected_pvalue.mif", output_header); + Image cluster_image_neg; + Image fwe_pvalue_image_neg; + Image uncorrected_pvalue_image_neg; vector_type perm_distribution (num_perms); std::shared_ptr perm_distribution_neg; vector_type default_cluster_output (num_vox); std::shared_ptr default_cluster_output_neg; vector_type tvalue_output (num_vox); - Stats::PermTest::empirical_vector_type empirical_tfce_statistic; + vector_type empirical_tfce_statistic; vector_type uncorrected_pvalue (num_vox); std::shared_ptr uncorrected_pvalue_neg; - bool compute_negative_contrast = get_options("negative").size() ? true : false; + const bool compute_negative_contrast = get_options("negative").size() ? true : false; if (compute_negative_contrast) { std::string cluster_neg_name (prefix); if (std::isfinite (cluster_forming_threshold)) cluster_neg_name.append ("cluster_sizes_neg.mif"); else cluster_neg_name.append ("tfce_neg.mif"); - cluster_image_neg = Image::create (cluster_neg_name, output_header); + cluster_image_neg = Image::create (cluster_neg_name, output_header); perm_distribution_neg.reset (new vector_type (num_perms)); default_cluster_output_neg.reset (new vector_type (num_vox)); - fwe_pvalue_image_neg = Image::create (prefix + "fwe_pvalue_neg.mif", output_header); + fwe_pvalue_image_neg = Image::create (prefix + "fwe_pvalue_neg.mif", output_header); uncorrected_pvalue_neg.reset (new vector_type (num_vox)); - uncorrected_pvalue_image_neg = Image::create (prefix + "uncorrected_pvalue_neg.mif", output_header); + uncorrected_pvalue_image_neg = Image::create (prefix + "uncorrected_pvalue_neg.mif", output_header); } { // Do permutation testing: - Math::Stats::GLMTTest glm (data, design, contrast); + Math::Stats::GLMTTest glm (data, design, contrast); // Suprathreshold clustering if (std::isfinite (cluster_forming_threshold)) { @@ -257,9 +254,9 @@ void run() { save_matrix (perm_distribution, prefix + "perm_dist.txt"); vector_type pvalue_output (num_vox); - Math::Stats::statistic2pvalue (perm_distribution, default_cluster_output, pvalue_output); + Math::Stats::Permutation::statistic2pvalue (perm_distribution, default_cluster_output, pvalue_output); { - ProgressBar progress ("generating output"); + ProgressBar progress ("generating output", num_vox); for (size_t i = 0; i < num_vox; i++) { for (size_t dim = 0; dim < cluster_image.ndim(); dim++) tvalue_image.index(dim) = cluster_image.index(dim) = fwe_pvalue_image.index(dim) = uncorrected_pvalue_image.index(dim) = mask_indices[i][dim]; @@ -270,22 +267,21 @@ void run() { ++progress; } } - { - if (compute_negative_contrast) { - save_matrix (*perm_distribution_neg, prefix + "perm_dist_neg.txt"); - vector_type pvalue_output_neg (num_vox); - Math::Stats::statistic2pvalue (*perm_distribution_neg, *default_cluster_output_neg, pvalue_output_neg); - - ProgressBar progress ("generating negative contrast output"); - for (size_t i = 0; i < num_vox; i++) { - for (size_t dim = 0; dim < cluster_image.ndim(); dim++) - cluster_image_neg.index(dim) = fwe_pvalue_image_neg.index(dim) = uncorrected_pvalue_image_neg.index(dim) = mask_indices[i][dim]; - cluster_image_neg.value() = (*default_cluster_output_neg)[i]; - fwe_pvalue_image_neg.value() = pvalue_output_neg[i]; - uncorrected_pvalue_image_neg.value() = (*uncorrected_pvalue_neg)[i]; - ++progress; - } - } + if (compute_negative_contrast) { + save_matrix (*perm_distribution_neg, prefix + "perm_dist_neg.txt"); + vector_type pvalue_output_neg (num_vox); + Math::Stats::Permutation::statistic2pvalue (*perm_distribution_neg, *default_cluster_output_neg, pvalue_output_neg); + + ProgressBar progress ("generating negative contrast output", num_vox); + for (size_t i = 0; i < num_vox; i++) { + for (size_t dim = 0; dim < cluster_image.ndim(); dim++) + cluster_image_neg.index(dim) = fwe_pvalue_image_neg.index(dim) = uncorrected_pvalue_image_neg.index(dim) = mask_indices[i][dim]; + cluster_image_neg.value() = (*default_cluster_output_neg)[i]; + fwe_pvalue_image_neg.value() = pvalue_output_neg[i]; + uncorrected_pvalue_image_neg.value() = (*uncorrected_pvalue_neg)[i]; + ++progress; + } } + } diff --git a/lib/math/stats/glm.cpp b/lib/math/stats/glm.cpp new file mode 100644 index 0000000000..ecd719c84f --- /dev/null +++ b/lib/math/stats/glm.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "math/stats/glm.h" + +#define GLM_BATCH_SIZE 1024 + +namespace MR +{ + namespace Math + { + namespace Stats + { + + + + namespace GLM + { + + matrix_type scale_contrasts (const matrix_type& contrasts, const matrix_type& design, const size_t degrees_of_freedom) + { + assert (contrasts.cols() == design.cols()); + const matrix_type XtX = design.transpose() * design; + const matrix_type pinv_XtX = (XtX.transpose() * XtX).fullPivLu().solve (XtX.transpose()); + matrix_type scaled_contrasts (contrasts); + + for (size_t n = 0; n < size_t(contrasts.rows()); ++n) { + auto pinv_XtX_c = pinv_XtX * contrasts.row(n).transpose(); + scaled_contrasts.row(n) *= std::sqrt (value_type(degrees_of_freedom) / contrasts.row(n).dot (pinv_XtX_c)); + } + return scaled_contrasts; + } + + + + void ttest (matrix_type& tvalues, + const matrix_type& design, + const matrix_type& pinv_design, + const matrix_type& measurements, + const matrix_type& scaled_contrasts, + matrix_type& betas, + matrix_type& residuals) + { + betas.noalias() = measurements * pinv_design; + residuals.noalias() = measurements - betas * design; + tvalues.noalias() = betas * scaled_contrasts; + for (size_t n = 0; n < size_t(tvalues.rows()); ++n) + tvalues.row(n).array() /= residuals.row(n).norm(); + } + + + + matrix_type solve_betas (const matrix_type& measurements, const matrix_type& design) + { + return design.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(measurements.transpose()); + } + + + + matrix_type abs_effect_size (const matrix_type& measurements, const matrix_type& design, const matrix_type& contrast) + { + return contrast * solve_betas (measurements, design); + } + + + matrix_type stdev (const matrix_type& measurements, const matrix_type& design) + { + matrix_type residuals = measurements.transpose() - design * solve_betas (measurements, design); //TODO + residuals = residuals.array().pow(2.0); + matrix_type one_over_dof (1, measurements.cols()); //TODO supply transposed measurements + one_over_dof.fill (1.0 / value_type(design.rows()-Math::rank (design))); + return (one_over_dof * residuals).array().sqrt(); + } + + + matrix_type std_effect_size (const matrix_type& measurements, const matrix_type& design, const matrix_type& contrast) + { + return abs_effect_size (measurements, design, contrast).array() / stdev (measurements, design).array(); + } + } + + + + + + + + + GLMTTest::GLMTTest (const matrix_type& measurements, const matrix_type& design, const matrix_type& contrast) : + y (measurements), + X (design), + scaled_contrasts (GLM::scale_contrasts (contrast, X, X.rows()-rank(X)).transpose()) + { + pinvX = Math::pinv (X); + } + + + + void GLMTTest::operator() (const std::vector& perm_labelling, vector_type& stats, value_type& max_stat, value_type& min_stat) const + { + stats = vector_type::Zero (y.rows()); + matrix_type tvalues, betas, residuals, SX, pinvSX; + + SX.resize (X.rows(), X.cols()); + pinvSX.resize (pinvX.rows(), pinvX.cols()); + for (ssize_t i = 0; i < X.rows(); ++i) { + SX.row(i) = X.row (perm_labelling[i]); + pinvSX.col(i) = pinvX.col (perm_labelling[i]); + } + + pinvSX.transposeInPlace(); + SX.transposeInPlace(); + for (ssize_t i = 0; i < y.rows(); i += GLM_BATCH_SIZE) { + const matrix_type tmp = y.block (i, 0, std::min (GLM_BATCH_SIZE, (int)(y.rows()-i)), y.cols()); + GLM::ttest (tvalues, SX, pinvSX, tmp, scaled_contrasts, betas, residuals); + for (ssize_t n = 0; n < tvalues.rows(); ++n) { + value_type val = tvalues(n,0); + if (std::isfinite (val)) { + if (val > max_stat) + max_stat = val; + if (val < min_stat) + min_stat = val; + } else { + val = value_type(0); + } + stats[i+n] = val; + } + } + } + + + + + } + } +} + diff --git a/lib/math/stats/glm.h b/lib/math/stats/glm.h index 8d6bdc9f80..714fb54d34 100644 --- a/lib/math/stats/glm.h +++ b/lib/math/stats/glm.h @@ -15,10 +15,8 @@ #ifndef __math_stats_glm_h__ #define __math_stats_glm_h__ -#include "types.h" #include "math/least_squares.h" - -#define GLM_BATCH_SIZE 1024 +#include "math/stats/typedefs.h" namespace MR { @@ -26,29 +24,16 @@ namespace MR { namespace Stats { + + + namespace GLM { - //! scale contrasts for use in t-test /*! Note each row of the contrast matrix will be treated as an independent contrast. The number * of elements in each contrast vector must equal the number of columns in the design matrix */ - template - inline Eigen::Matrix scale_contrasts (const Eigen::Matrix& contrasts, - const Eigen::Matrix& design, - size_t degrees_of_freedom) - { - assert (contrasts.cols() == design.cols()); - Eigen::Matrix XtX = (design.transpose() * design).template cast(); - Eigen::Matrix pinv_XtX = ((XtX.transpose() * XtX).fullPivLu().solve (XtX.transpose())).template cast(); - Eigen::Matrix scaled_contrasts (contrasts); - - for (size_t n = 0; n < size_t(contrasts.rows()); ++n) { - Eigen::Matrix pinv_XtX_c = pinv_XtX * contrasts.row(n).transpose(); - scaled_contrasts.row(n) *= std::sqrt (ValueType (degrees_of_freedom) / contrasts.row(n).dot (pinv_XtX_c)); - } - return scaled_contrasts; - } + matrix_type scale_contrasts (const matrix_type& contrasts, const matrix_type& design, const size_t degrees_of_freedom); @@ -59,22 +44,15 @@ namespace MR * * Note also that the contrast matrix should already have been scaled * using the GLM::scale_contrasts() function. */ - template - inline void ttest ( - Eigen::Matrix& tvalues, - const Eigen::Matrix& design, - const Eigen::Matrix& pinv_design, - const Eigen::Matrix& measurements, - const Eigen::Matrix& scaled_contrasts, - Eigen::Matrix& betas, - Eigen::Matrix& residuals) - { - betas.noalias() = measurements * pinv_design; - residuals.noalias() = measurements - betas * design; - tvalues.noalias() = betas * scaled_contrasts; - for (size_t n = 0; n < size_t(tvalues.rows()); ++n) - tvalues.row(n).array() /= residuals.row(n).norm(); - } + void ttest (matrix_type& tvalues, + const matrix_type& design, + const matrix_type& pinv_design, + const matrix_type& measurements, + const matrix_type& scaled_contrasts, + matrix_type& betas, + matrix_type& residuals); + + /** \addtogroup Statistics @{ */ @@ -83,11 +61,7 @@ namespace MR * @param design the design matrix (unlike other packages a column of ones is NOT automatically added for correlation analysis) * @return the matrix containing the output effect */ - template - Eigen::Matrix solve_betas (const Eigen::Matrix& measurements, - const Eigen::Matrix& design) { - return design.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(measurements.transpose()); - } + matrix_type solve_betas (const matrix_type& measurements, const matrix_type& design); @@ -97,12 +71,8 @@ namespace MR * @param contrast a matrix defining the group difference * @return the matrix containing the output effect */ - template - Eigen::Matrix abs_effect_size (const Eigen::Matrix& measurements, - const Eigen::Matrix& design, - const Eigen::Matrix& contrast) { - return contrast * solve_betas (measurements, design); - } + matrix_type abs_effect_size (const matrix_type& measurements, const matrix_type& design, const matrix_type& contrast); + /*! Compute the pooled standard deviation @@ -110,15 +80,8 @@ namespace MR * @param design the design matrix (unlike other packages a column of ones is NOT automatically added for correlation analysis) * @return the matrix containing the output standard deviation size */ - template - Eigen::Matrix stdev (const Eigen::Matrix& measurements, - const Eigen::Matrix& design) { - Eigen::Matrix residuals = measurements.transpose() - design * solve_betas (measurements, design); //TODO - residuals = residuals.array().pow(2.0); - Eigen::Matrix one_over_dof (1, measurements.cols()); //TODO supply transposed measurements - one_over_dof.fill(1.0 / ValueType(design.rows()-Math::rank(design))); - return (one_over_dof * residuals).array().sqrt(); - } + matrix_type stdev (const matrix_type& measurements, const matrix_type& design); + /*! Compute cohen's d, the standardised effect size between two means @@ -127,37 +90,25 @@ namespace MR * @param contrast a matrix defining the group difference * @return the matrix containing the output standardised effect size */ - template - Eigen::Matrix std_effect_size (const Eigen::Matrix& measurements, - const Eigen::Matrix& design, - const Eigen::Matrix& contrast) { - return abs_effect_size (measurements, design, contrast).array() / stdev (measurements, design).array(); - } + matrix_type std_effect_size (const matrix_type& measurements, const matrix_type& design, const matrix_type& contrast); //! @} - } + + } // End GLM namespace + + /** \addtogroup Statistics @{ */ /*! A class to compute t-statistics using a General Linear Model. */ - template class GLMTTest { public: - typedef ValueType value_type; /*! * @param measurements a matrix storing the measured data for each subject in a column //TODO * @param design the design matrix (unlike other packages a column of ones is NOT automatically added for correlation analysis) * @param contrast a matrix containing the contrast of interest. */ - GLMTTest (const Eigen::Matrix& measurements, - const Eigen::Matrix& design, - const Eigen::Matrix& contrast) : - y (measurements), - X (design), - scaled_contrasts (GLM::scale_contrasts (contrast, X, X.rows()-rank(X)).transpose()) - { - pinvX = Math::pinv (X.template cast()).template cast(); - } + GLMTTest (const matrix_type& measurements, const matrix_type& design, const matrix_type& contrast); /*! Compute the t-statistics * @param perm_labelling a vector to shuffle the rows in the design matrix (for permutation testing) @@ -165,45 +116,14 @@ namespace MR * @param max_stat the maximum t-statistic * @param min_stat the minimum t-statistic */ - void operator() (const std::vector& perm_labelling, Eigen::Array& stats, - ValueType& max_stat, ValueType& min_stat) const - { - stats = Eigen::Array::Zero (y.rows()); - Eigen::Matrix tvalues, betas, residuals, SX, pinvSX; - - SX.resize (X.rows(), X.cols()); - pinvSX.resize (pinvX.rows(), pinvX.cols()); - for (ssize_t i = 0; i < X.rows(); ++i) { - SX.row(i) = X.row (perm_labelling[i]); - pinvSX.col(i) = pinvX.col (perm_labelling[i]); - } - - pinvSX.transposeInPlace(); - SX.transposeInPlace(); - for (ssize_t i = 0; i < y.rows(); i += GLM_BATCH_SIZE) { - const Eigen::Matrix tmp = y.block (i, 0, std::min (GLM_BATCH_SIZE, (int)(y.rows()-i)), y.cols()); - GLM::ttest (tvalues, SX, pinvSX, tmp, scaled_contrasts, betas, residuals); - for (ssize_t n = 0; n < tvalues.rows(); ++n) { - ValueType val = tvalues(n,0); - if (std::isfinite (val)) { - if (val > max_stat) - max_stat = val; - if (val < min_stat) - min_stat = val; - } else { - val = ValueType(0); - } - stats[i+n] = val; - } - } - } + void operator() (const std::vector& perm_labelling, vector_type& stats, value_type& max_stat, value_type& min_stat) const; size_t num_subjects () const { return y.cols(); } size_t num_elements () const { return y.rows(); } protected: - const Eigen::Matrix& y; - Eigen::Matrix X, pinvX, scaled_contrasts; + const matrix_type& y; + matrix_type X, pinvX, scaled_contrasts; }; //! @} diff --git a/lib/math/stats/permutation.cpp b/lib/math/stats/permutation.cpp new file mode 100644 index 0000000000..72a045c62a --- /dev/null +++ b/lib/math/stats/permutation.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "math/stats/permutation.h" + +namespace MR +{ + namespace Math + { + namespace Stats + { + namespace Permutation + { + + + + bool is_duplicate (const std::vector& v1, const std::vector& v2) + { + for (size_t i = 0; i < v1.size(); i++) { + if (v1[i] != v2[i]) + return false; + } + return true; + } + + + + bool is_duplicate (const std::vector& perm, + const std::vector >& previous_permutations) + { + for (size_t p = 0; p < previous_permutations.size(); p++) { + if (is_duplicate (perm, previous_permutations[p])) + return true; + } + return false; + } + + + + void generate (const size_t num_perms, + const size_t num_subjects, + std::vector >& permutations, + const bool include_default) + { + permutations.clear(); + std::vector default_labelling (num_subjects); + for (size_t i = 0; i < num_subjects; ++i) + default_labelling[i] = i; + size_t p = 0; + if (include_default) { + permutations.push_back (default_labelling); + ++p; + } + for (;p < num_perms; ++p) { + std::vector permuted_labelling (default_labelling); + do { + std::random_shuffle (permuted_labelling.begin(), permuted_labelling.end()); + } while (is_duplicate (permuted_labelling, permutations)); + permutations.push_back (permuted_labelling); + } + } + + + + void statistic2pvalue (const vector_type& perm_dist, const vector_type& stats, vector_type& pvalues) + { + std::vector permutations; + permutations.reserve (perm_dist.size()); + for (ssize_t i = 0; i != perm_dist.size(); ++i) + permutations.push_back (perm_dist[i]); + std::sort (permutations.begin(), permutations.end()); + pvalues.resize (stats.size()); + for (size_t i = 0; i < size_t(stats.size()); ++i) { + if (stats[i] > 0.0) { + value_type pvalue = 1.0; + for (size_t j = 0; j < size_t(permutations.size()); ++j) { + if (stats[i] < permutations[j]) { + pvalue = value_type(j) / value_type(permutations.size()); + break; + } + } + pvalues[i] = pvalue; + } else { + pvalues[i] = 0.0; + } + } + } + + + + } + } + } +} diff --git a/lib/math/stats/permutation.h b/lib/math/stats/permutation.h index 9562e9b30d..a2635006d5 100644 --- a/lib/math/stats/permutation.h +++ b/lib/math/stats/permutation.h @@ -15,7 +15,9 @@ #ifndef __math_stats_permutation_h__ #define __math_stats_permutation_h__ -#include "math/math.h" +#include + +#include "math/stats/typedefs.h" namespace MR { @@ -23,85 +25,32 @@ namespace MR { namespace Stats { + namespace Permutation + { - inline bool is_duplicate_vector (const std::vector& v1, const std::vector& v2) - { - for (size_t i = 0; i < v1.size(); i++) { - if (v1[i] != v2[i]) - return false; - } - return true; - } + typedef Math::Stats::value_type value_type; + typedef Math::Stats::vector_type vector_type; - inline bool is_duplicate_permutation (const std::vector& perm, - const std::vector >& previous_permutations) - { - for (size_t p = 0; p < previous_permutations.size(); p++) { - if (is_duplicate_vector (perm, previous_permutations[p])) - return true; - } - return false; - } - // Note that this function does not take into account grouping of subjects and therefore generated - // permutations are not guaranteed to be unique wrt the computed test statistic. - // If the number of subjects is large then the likelihood of generating duplicates is low. - inline void generate_permutations (const size_t num_perms, - const size_t num_subjects, - std::vector >& permutations, - bool include_default) - { - permutations.clear(); - std::vector default_labelling (num_subjects); - for (size_t i = 0; i < num_subjects; ++i) - default_labelling[i] = i; - size_t p = 0; - if (include_default) { - permutations.push_back (default_labelling); - ++p; - } - for (;p < num_perms; ++p) { - std::vector permuted_labelling (default_labelling); - do { - std::random_shuffle (permuted_labelling.begin(), permuted_labelling.end()); - } while (is_duplicate_permutation (permuted_labelling, permutations)); - permutations.push_back (permuted_labelling); - } - } + bool is_duplicate (const std::vector&, const std::vector&); + bool is_duplicate (const std::vector&, const std::vector >&); + // Note that this function does not take into account grouping of subjects and therefore generated + // permutations are not guaranteed to be unique wrt the computed test statistic. + // If the number of subjects is large then the likelihood of generating duplicates is low. + void generate (const size_t num_perms, + const size_t num_subjects, + std::vector >& permutations, + const bool include_default); - template - inline void statistic2pvalue (const VectorType& perm_dist, - const VectorType& stats, - VectorType& pvalues) - { - typedef typename container_value_type::type value_type; - std::vector permutations (perm_dist.size(), 0); - for (ssize_t i = 0; i < perm_dist.size(); i++) - permutations[i] = perm_dist[i]; - std::sort (permutations.begin(), permutations.end()); - pvalues.resize (stats.size()); - for (size_t i = 0; i < size_t(stats.size()); ++i) { - if (stats[i] > 0.0) { - value_type pvalue = 1.0; - for (size_t j = 0; j < size_t(permutations.size()); ++j) { - if (stats[i] < permutations[j]) { - pvalue = value_type(j) / value_type(permutations.size()); - break; - } - } - pvalues[i] = pvalue; - } - else - pvalues[i] = 0.0; - } - } + void statistic2pvalue (const vector_type& perm_dist, const vector_type& stats, vector_type& pvalues); + } } } } diff --git a/lib/math/stats/typedefs.h b/lib/math/stats/typedefs.h new file mode 100644 index 0000000000..29a6d92228 --- /dev/null +++ b/lib/math/stats/typedefs.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ +#ifndef __math_stats_types_h__ +#define __math_stats_types_h__ + +#include "types.h" + +#include + +namespace MR +{ + namespace Math + { + namespace Stats + { + + + + typedef MR::default_type value_type; + typedef Eigen::Matrix matrix_type; + typedef Eigen::Array vector_type; + + + + } + } +} + + +#endif diff --git a/src/stats/cfe.cpp b/src/stats/cfe.cpp new file mode 100644 index 0000000000..38e638e83b --- /dev/null +++ b/src/stats/cfe.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "stats/cfe.h" + +namespace MR +{ + namespace Stats + { + namespace CFE + { + + + + TrackProcessor::TrackProcessor (Image& fixel_indexer, + const std::vector& fixel_directions, + std::vector& fixel_TDI, + std::vector >& connectivity_matrix, + const value_type angular_threshold): + fixel_indexer (fixel_indexer) , + fixel_directions (fixel_directions), + fixel_TDI (fixel_TDI), + connectivity_matrix (connectivity_matrix), + angular_threshold_dp (angular_threshold * (Math::pi/180.0)) { } + + + + bool TrackProcessor::operator () (const SetVoxelDir& in) + { + // For each voxel tract tangent, assign to a fixel + std::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; + int32_t first_index = fixel_indexer.value(); + if (first_index >= 0) { + fixel_indexer.index(3) = 1; + int32_t last_index = first_index + fixel_indexer.value(); + int32_t closest_fixel_index = -1; + value_type largest_dp = 0.0; + direction_type dir (i->get_dir().normalized()); + for (int32_t j = first_index; j < last_index; ++j) { + const value_type dp = std::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]++; + } + } + } + + try { + for (size_t i = 0; i < tract_fixel_indices.size(); i++) { + for (size_t j = i + 1; j < tract_fixel_indices.size(); j++) { + connectivity_matrix[tract_fixel_indices[i]][tract_fixel_indices[j]].value++; + connectivity_matrix[tract_fixel_indices[j]][tract_fixel_indices[i]].value++; + } + } + return true; + } catch (...) { + throw Exception ("Error assigning memory for CFE connectivity matrix"); + return false; + } + } + + + + + + + + + Enhancer::Enhancer (const std::vector >& connectivity_map, + const value_type dh, + const value_type E, + const value_type H) : + connectivity_map (connectivity_map), + dh (dh), + E (E), + H (H) { } + + + + value_type Enhancer::operator() (const value_type max_stat, const vector_type& stats, vector_type& enhanced_stats) const + { + enhanced_stats = vector_type::Zero (stats.size()); + value_type max_enhanced_stat = 0.0; + for (size_t fixel = 0; fixel < connectivity_map.size(); ++fixel) { + std::map::const_iterator connected_fixel; + for (value_type h = this->dh; h < stats[fixel]; h += this->dh) { + value_type extent = 0.0; + for (connected_fixel = connectivity_map[fixel].begin(); connected_fixel != connectivity_map[fixel].end(); ++connected_fixel) + if (stats[connected_fixel->first] > h) + extent += connected_fixel->second.value; + enhanced_stats[fixel] += std::pow (extent, E) * std::pow (h, H); + } + if (enhanced_stats[fixel] > max_enhanced_stat) + max_enhanced_stat = enhanced_stats[fixel]; + } + + return max_enhanced_stat; + } + + + + } + } +} diff --git a/src/stats/cfe.h b/src/stats/cfe.h index d9860ba1d5..ed4858f111 100644 --- a/src/stats/cfe.h +++ b/src/stats/cfe.h @@ -15,8 +15,11 @@ #ifndef __stats_cfe_h__ #define __stats_cfe_h__ -#include "math/math.h" #include "image.h" +#include "image_helpers.h" +#include "math/math.h" +#include "math/stats/typedefs.h" + #include "dwi/tractography/mapping/mapper.h" namespace MR @@ -26,8 +29,11 @@ namespace MR namespace CFE { - typedef float value_type; - typedef Eigen::Array vector_type; + typedef Math::Stats::value_type value_type; + typedef Math::Stats::vector_type vector_type; + typedef float connectivity_value_type; + typedef Eigen::Matrix direction_type; + typedef Eigen::Array connectivity_vector_type; typedef DWI::Tractography::Mapping::SetVoxelDir SetVoxelDir; @@ -38,7 +44,8 @@ namespace MR class connectivity { public: connectivity () : value (0.0) { } - value_type value; + connectivity (const connectivity_value_type v) : value (0.0) { } + connectivity_value_type value; }; @@ -51,66 +58,21 @@ namespace MR public: TrackProcessor (Image& fixel_indexer, - const std::vector >& fixel_directions, + const std::vector& fixel_directions, std::vector& fixel_TDI, std::vector >& connectivity_matrix, - value_type angular_threshold): - fixel_indexer (fixel_indexer) , - fixel_directions (fixel_directions), - fixel_TDI (fixel_TDI), - connectivity_matrix (connectivity_matrix) { - angular_threshold_dp = cos (angular_threshold * (Math::pi/180.0)); - } - - bool operator () (SetVoxelDir& in) - { - // For each voxel tract tangent, assign to a fixel - std::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; - int32_t first_index = fixel_indexer.value(); - if (first_index >= 0) { - fixel_indexer.index(3) = 1; - int32_t last_index = first_index + fixel_indexer.value(); - int32_t closest_fixel_index = -1; - value_type largest_dp = 0.0; - Eigen::Vector3f dir (i->get_dir()); - dir.normalize(); - for (int32_t j = first_index; j < last_index; ++j) { - value_type dp = std::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]++; - } - } - } - - try { - for (size_t i = 0; i < tract_fixel_indices.size(); i++) { - for (size_t j = i + 1; j < tract_fixel_indices.size(); j++) { - connectivity_matrix[tract_fixel_indices[i]][tract_fixel_indices[j]].value++; - connectivity_matrix[tract_fixel_indices[j]][tract_fixel_indices[i]].value++; - } - } - return true; - } catch (...) { - throw Exception ("Error assigning memory for CFE connectivity matrix"); - return false; - } - } + const value_type angular_threshold); + + + bool operator () (const SetVoxelDir& in); + private: Image fixel_indexer; - const std::vector& fixel_directions; + const std::vector& fixel_directions; std::vector& fixel_TDI; std::vector >& connectivity_matrix; - value_type angular_threshold_dp; + default_type angular_threshold_dp; }; @@ -119,29 +81,11 @@ namespace MR class Enhancer { public: Enhancer (const std::vector >& connectivity_map, - const value_type dh, const value_type E, const value_type H) : - connectivity_map (connectivity_map), dh (dh), E (E), H (H) { } - - value_type operator() (const value_type max_stat, const vector_type& stats, - vector_type& enhanced_stats) const - { - enhanced_stats = vector_type::Zero(stats.size()); - value_type max_enhanced_stat = 0.0; - for (size_t fixel = 0; fixel < connectivity_map.size(); ++fixel) { - std::map::const_iterator connected_fixel; - for (value_type h = this->dh; h < stats[fixel]; h += this->dh) { - value_type extent = 0.0; - for (connected_fixel = connectivity_map[fixel].begin(); connected_fixel != connectivity_map[fixel].end(); ++connected_fixel) - if (stats[connected_fixel->first] > h) - extent += connected_fixel->second.value; - enhanced_stats[fixel] += std::pow (extent, E) * std::pow (h, H); - } - if (enhanced_stats[fixel] > max_enhanced_stat) - max_enhanced_stat = enhanced_stats[fixel]; - } - - return max_enhanced_stat; - } + const value_type dh, const value_type E, const value_type H); + + + value_type operator() (const value_type max_stat, const vector_type& stats, vector_type& enhanced_stats) const; + protected: const std::vector >& connectivity_map; diff --git a/src/stats/cluster.cpp b/src/stats/cluster.cpp new file mode 100644 index 0000000000..7d27bde59a --- /dev/null +++ b/src/stats/cluster.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "stats/cluster.h" + +#include + +namespace MR +{ + namespace Stats + { + namespace Cluster + { + + + + value_type ClusterSize::operator() (const value_type /*unused*/, const vector_type& stats, vector_type& get_cluster_sizes) const + { + std::vector clusters; + std::vector labels (stats.size(), 0); + connector.run (clusters, labels, stats, cluster_forming_threshold); + get_cluster_sizes.resize (stats.size()); + for (size_t i = 0; i < size_t(stats.size()); ++i) + get_cluster_sizes[i] = labels[i] ? clusters[labels[i]-1].size : 0.0; + + return clusters.size() ? std::max_element (clusters.begin(), clusters.end())->size : 0.0; + } + + + + } + } +} diff --git a/src/stats/cluster.h b/src/stats/cluster.h index 4f9f87ba9a..15d1d78b5e 100644 --- a/src/stats/cluster.h +++ b/src/stats/cluster.h @@ -12,11 +12,12 @@ * For more details, see www.mrtrix.org * */ + #ifndef __stats_cluster_h__ #define __stats_cluster_h__ - #include "filter/connected_components.h" +#include "math/stats/typedefs.h" namespace MR { @@ -25,8 +26,11 @@ namespace MR namespace Cluster { - typedef float value_type; - typedef Eigen::Array vector_type; + + + typedef Math::Stats::value_type value_type; + typedef Math::Stats::vector_type vector_type; + /** \addtogroup Statistics @@ -36,26 +40,18 @@ namespace MR ClusterSize (const Filter::Connector& connector, value_type cluster_forming_threshold) : connector (connector), cluster_forming_threshold (cluster_forming_threshold) { } - value_type operator() (const value_type unused, const vector_type& stats, - vector_type& get_cluster_sizes) const - { - std::vector clusters; - std::vector labels (stats.size(), 0); - connector.run (clusters, labels, stats, cluster_forming_threshold); - get_cluster_sizes.resize (stats.size()); - for (size_t i = 0; i < size_t(stats.size()); ++i) - get_cluster_sizes[i] = labels[i] ? clusters[labels[i]-1].size : 0.0; - return clusters.size() ? std::max_element (clusters.begin(), clusters.end())->size : 0.0; - } + value_type operator() (const value_type /*unused*/, const vector_type& stats, vector_type& get_cluster_sizes) const; + protected: const Filter::Connector& connector; - value_type cluster_forming_threshold; + const value_type cluster_forming_threshold; }; - //! @} + + } } } diff --git a/src/stats/permstack.cpp b/src/stats/permstack.cpp new file mode 100644 index 0000000000..0cb089282d --- /dev/null +++ b/src/stats/permstack.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "stats/permstack.h" + +namespace MR +{ + namespace Stats + { + namespace PermTest + { + + + + PermutationStack::PermutationStack (const size_t num_permutations, const size_t num_samples, const std::string msg, const bool include_default) : + num_permutations (num_permutations), + current_permutation (0), + progress (msg, num_permutations) + { + Math::Stats::Permutation::generate (num_permutations, num_samples, permutations, include_default); + } + + + + size_t PermutationStack::next() + { + std::lock_guard lock (permutation_mutex); + size_t index = current_permutation++; + if (index < permutations.size()) + ++progress; + return index; + } + + + + } + } +} diff --git a/src/stats/permstack.h b/src/stats/permstack.h new file mode 100644 index 0000000000..38df608cf0 --- /dev/null +++ b/src/stats/permstack.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __stats_permstack_h__ +#define __stats_permstack_h__ + +#include +#include +#include + +#include "progressbar.h" +#include "math/stats/permutation.h" + +namespace MR +{ + namespace Stats + { + namespace PermTest + { + + + + class PermutationStack { + public: + PermutationStack (const size_t num_permutations, const size_t num_samples, const std::string msg, const bool include_default = true); + + size_t next(); + + const std::vector& permutation (size_t index) const { + return permutations[index]; + } + + const size_t num_permutations; + + protected: + size_t current_permutation; + ProgressBar progress; + std::vector< std::vector > permutations; + std::mutex permutation_mutex; + }; + + + + + } + } +} + +#endif diff --git a/src/stats/permtest.h b/src/stats/permtest.h index 1801d6548f..572880e9f8 100644 --- a/src/stats/permtest.h +++ b/src/stats/permtest.h @@ -12,6 +12,7 @@ * For more details, see www.mrtrix.org * */ + #ifndef __stats_permtest_h__ #define __stats_permtest_h__ @@ -20,9 +21,12 @@ #include "progressbar.h" #include "thread.h" +#include "thread_queue.h" #include "math/math.h" #include "math/stats/permutation.h" -#include "thread_queue.h" +#include "math/stats/typedefs.h" + +#include "stats/permstack.h" namespace MR { @@ -32,58 +36,31 @@ namespace MR { - typedef std::vector empirical_vector_type; - - - class PermutationStack { - public: - PermutationStack (size_t num_permutations, size_t num_samples, std::string msg, bool include_default = true) : - num_permutations (num_permutations), - current_permutation (0), - progress (msg, num_permutations) { - Math::Stats::generate_permutations (num_permutations, num_samples, permutations, include_default); - } - - size_t next () { - std::lock_guard lock (permutation_mutex); - size_t index = current_permutation++; - if (index < permutations.size()) - ++progress; - return index; - } - const std::vector& permutation (size_t index) const { - return permutations[index]; - } - - const size_t num_permutations; - - protected: - size_t current_permutation; - ProgressBar progress; - std::vector< std::vector > permutations; - std::mutex permutation_mutex; - }; + typedef Math::Stats::value_type value_type; + typedef Math::Stats::vector_type vector_type; /*! A class to pre-compute the empirical enhanced statistic image for non-stationarity correction */ - template + template class PreProcessor { public: - PreProcessor (PermutationStack& permutation_stack, const StatsType& stats_calculator, - const EnchancementType& enhancer, empirical_vector_type& global_enhanced_sum, + PreProcessor (PermutationStack& permutation_stack, + const StatsType& stats_calculator, + const EnhancementType& enhancer, + vector_type& global_enhanced_sum, std::vector& global_enhanced_count) : perm_stack (permutation_stack), stats_calculator (stats_calculator), enhancer (enhancer), global_enhanced_sum (global_enhanced_sum), - global_enhanced_count (global_enhanced_count), enhanced_sum (global_enhanced_sum.size(), 0.0), + global_enhanced_count (global_enhanced_count), enhanced_sum (vector_type::Zero (global_enhanced_sum.size())), enhanced_count (global_enhanced_sum.size(), 0.0), stats (global_enhanced_sum.size()), enhanced_stats (global_enhanced_sum.size()), mutex (new std::mutex()) {} ~PreProcessor () { std::lock_guard lock (*mutex); - for (size_t i = 0; i < global_enhanced_sum.size(); ++i) { + for (ssize_t i = 0; i < global_enhanced_sum.size(); ++i) { global_enhanced_sum[i] += enhanced_sum[i]; global_enhanced_count[i] += enhanced_count[i]; } @@ -100,10 +77,10 @@ namespace MR void process_permutation (size_t index) { - typename StatsType::value_type max_stat = 0.0, min_stat = 0.0; + value_type max_stat = 0.0, min_stat = 0.0; stats_calculator (perm_stack.permutation (index), stats, max_stat, min_stat); enhancer (max_stat, stats, enhanced_stats); - for (size_t i = 0; i < size_t(enhanced_stats.size()); ++i) { + for (ssize_t i = 0; i < enhanced_stats.size(); ++i) { if (enhanced_stats[i] > 0.0) { enhanced_sum[i] += enhanced_stats[i]; enhanced_count[i]++; @@ -113,13 +90,13 @@ namespace MR PermutationStack& perm_stack; StatsType stats_calculator; - EnchancementType enhancer; - empirical_vector_type& global_enhanced_sum; + EnhancementType enhancer; + vector_type& global_enhanced_sum; std::vector& global_enhanced_count; - empirical_vector_type enhanced_sum; + vector_type enhanced_sum; std::vector enhanced_count; - Eigen::Array stats; - Eigen::Array enhanced_stats; + vector_type stats; + vector_type enhanced_stats; std::shared_ptr mutex; }; @@ -127,14 +104,19 @@ namespace MR /*! A class to perform the permutation testing */ - template + template class Processor { public: - Processor (PermutationStack& permutation_stack, const StatsType& stats_calculator, - const EnhancementType& enhancer, const empirical_vector_type& empirical_enhanced_statistics, - const VectorType& default_enhanced_statistics, const std::shared_ptr default_enhanced_statistics_neg, - VectorType& perm_dist_pos, std::shared_ptr perm_dist_neg, - std::vector& global_uncorrected_pvalue_counter, std::shared_ptr > global_uncorrected_pvalue_counter_neg) : + Processor (PermutationStack& permutation_stack, + const StatsType& stats_calculator, + const EnhancementType& enhancer, + const vector_type& empirical_enhanced_statistics, + const vector_type& default_enhanced_statistics, + const std::shared_ptr default_enhanced_statistics_neg, + vector_type& perm_dist_pos, + std::shared_ptr perm_dist_neg, + std::vector& global_uncorrected_pvalue_counter, + std::shared_ptr< std::vector > global_uncorrected_pvalue_counter_neg) : perm_stack (permutation_stack), stats_calculator (stats_calculator), enhancer (enhancer), empirical_enhanced_statistics (empirical_enhanced_statistics), default_enhanced_statistics (default_enhanced_statistics), default_enhanced_statistics_neg (default_enhanced_statistics_neg), @@ -171,41 +153,41 @@ namespace MR void process_permutation (size_t index) { - typename container_value_type::type max_stat = 0.0, min_stat = 0.0; + value_type max_stat = 0.0, min_stat = 0.0; stats_calculator (perm_stack.permutation (index), statistics, max_stat, min_stat); perm_dist_pos(index) = enhancer (max_stat, statistics, enhanced_statistics); if (empirical_enhanced_statistics.size()) { perm_dist_pos(index) = 0.0; - for (size_t i = 0; i < size_t(enhanced_statistics.size()); ++i) { + for (ssize_t i = 0; i < enhanced_statistics.size(); ++i) { enhanced_statistics[i] /= empirical_enhanced_statistics[i]; if (enhanced_statistics[i] > perm_dist_pos(index)) perm_dist_pos(index) = enhanced_statistics[i]; } } - for (size_t i = 0; i < size_t(enhanced_statistics.size()); ++i) { + for (ssize_t i = 0; i < enhanced_statistics.size(); ++i) { if (default_enhanced_statistics[i] > enhanced_statistics[i]) uncorrected_pvalue_counter[i]++; } // Compute the opposite contrast if (perm_dist_neg) { - for (size_t i = 0; i < size_t(statistics.size()); ++i) + for (ssize_t i = 0; i < statistics.size(); ++i) statistics[i] = -statistics[i]; (*perm_dist_neg)(index) = enhancer (-min_stat, statistics, enhanced_statistics); if (empirical_enhanced_statistics.size()) { (*perm_dist_neg)(index) = 0.0; - for (size_t i = 0; i < size_t(enhanced_statistics.size()); ++i) { + for (ssize_t i = 0; i < enhanced_statistics.size(); ++i) { enhanced_statistics[i] /= empirical_enhanced_statistics[i]; if (enhanced_statistics[i] > (*perm_dist_neg)(index)) (*perm_dist_neg)(index) = enhanced_statistics[i]; } } - for (size_t i = 0; i < size_t(enhanced_statistics.size()); ++i) { + for (ssize_t i = 0; i < enhanced_statistics.size(); ++i) { if ((*default_enhanced_statistics_neg)[i] > enhanced_statistics[i]) (*uncorrected_pvalue_counter_neg)[i]++; } @@ -216,15 +198,15 @@ namespace MR PermutationStack& perm_stack; StatsType stats_calculator; EnhancementType enhancer; - const empirical_vector_type& empirical_enhanced_statistics; - const VectorType& default_enhanced_statistics; - const std::shared_ptr default_enhanced_statistics_neg; - VectorType statistics; - VectorType enhanced_statistics; + const vector_type& empirical_enhanced_statistics; + const vector_type& default_enhanced_statistics; + const std::shared_ptr default_enhanced_statistics_neg; + vector_type statistics; + vector_type enhanced_statistics; std::vector uncorrected_pvalue_counter; std::shared_ptr > uncorrected_pvalue_counter_neg; - VectorType& perm_dist_pos; - std::shared_ptr perm_dist_neg; + vector_type& perm_dist_pos; + std::shared_ptr perm_dist_neg; std::vector& global_uncorrected_pvalue_counter; std::shared_ptr > global_uncorrected_pvalue_counter_neg; @@ -234,8 +216,8 @@ namespace MR // Precompute the empircal test statistic for non-stationarity adjustment template - inline void precompute_empirical_stat (const StatsType& stats_calculator, const EnhancementType& enhancer, - size_t num_permutations, empirical_vector_type& empirical_statistic) + void precompute_empirical_stat (const StatsType& stats_calculator, const EnhancementType& enhancer, + const size_t num_permutations, vector_type& empirical_statistic) { std::vector global_enhanced_count (empirical_statistic.size(), 0); PermutationStack preprocessor_permutations (num_permutations, @@ -246,7 +228,7 @@ namespace MR empirical_statistic, global_enhanced_count); auto preprocessor_threads = Thread::run (Thread::multi (preprocessor), "preprocessor threads"); } - for (size_t i = 0; i < empirical_statistic.size(); ++i) { + for (ssize_t i = 0; i < empirical_statistic.size(); ++i) { if (global_enhanced_count[i] > 0) empirical_statistic[i] /= static_cast (global_enhanced_count[i]); } @@ -255,37 +237,39 @@ namespace MR // Precompute the default statistic image and enhanced statistic. We need to precompute this for calculating the uncorrected p-values. - template - inline void precompute_default_permutation (const StatsType& stats_calculator, const EnhancementType& enhancer, - const empirical_vector_type empirical_enhanced_statistic, - VectorType& default_enhanced_statistics, std::shared_ptr default_enhanced_statistics_neg, - VectorType& default_statistics) + template + void precompute_default_permutation (const StatsType& stats_calculator, + const EnhancementType& enhancer, + const vector_type& empirical_enhanced_statistic, + vector_type& default_enhanced_statistics, + std::shared_ptr default_enhanced_statistics_neg, + vector_type& default_statistics) { std::vector default_labelling (stats_calculator.num_subjects()); for (size_t i = 0; i < default_labelling.size(); ++i) default_labelling[i] = i; - typename container_value_type::type max_stat = 0.0, min_stat = 0.0; + value_type max_stat = 0.0, min_stat = 0.0; stats_calculator (default_labelling, default_statistics, max_stat, min_stat); max_stat = enhancer (max_stat, default_statistics, default_enhanced_statistics); if (empirical_enhanced_statistic.size()) { - for (size_t i = 0; i < size_t(default_statistics.size()); ++i) + for (ssize_t i = 0; i < default_statistics.size(); ++i) default_enhanced_statistics[i] /= empirical_enhanced_statistic[i]; } // Compute the opposite contrast if (default_enhanced_statistics_neg) { - for (size_t i = 0; i < size_t(default_statistics.size()); ++i) + for (ssize_t i = 0; i < default_statistics.size(); ++i) default_statistics[i] = -default_statistics[i]; max_stat = enhancer (-min_stat, default_statistics, *default_enhanced_statistics_neg); if (empirical_enhanced_statistic.size()) { - for (size_t i = 0; i < size_t(default_statistics.size()); ++i) + for (ssize_t i = 0; i < default_statistics.size(); ++i) (*default_enhanced_statistics_neg)[i] /= empirical_enhanced_statistic[i]; } // revert default_statistics to positive contrast for output - for (size_t i = 0; i < size_t(default_statistics.size()); ++i) + for (ssize_t i = 0; i < default_statistics.size(); ++i) default_statistics[i] = -default_statistics[i]; } } @@ -293,14 +277,18 @@ namespace MR - template - inline void run_permutations (const StatsType& stats_calculator, const EnhancementType& enhancer, size_t num_permutations, - const empirical_vector_type& empirical_enhanced_statistic, - const VectorType& default_enhanced_statistics, const std::shared_ptr default_enhanced_statistics_neg, - VectorType& perm_dist_pos, std::shared_ptr perm_dist_neg, - VectorType& uncorrected_pvalues, std::shared_ptr uncorrected_pvalues_neg) + template + inline void run_permutations (const StatsType& stats_calculator, + const EnhancementType& enhancer, + const size_t num_permutations, + const vector_type& empirical_enhanced_statistic, + const vector_type& default_enhanced_statistics, + const std::shared_ptr default_enhanced_statistics_neg, + vector_type& perm_dist_pos, + std::shared_ptr perm_dist_neg, + vector_type& uncorrected_pvalues, + std::shared_ptr uncorrected_pvalues_neg) { - std::vector global_uncorrected_pvalue_count (stats_calculator.num_elements(), 0); std::shared_ptr< std::vector > global_uncorrected_pvalue_count_neg; if (perm_dist_neg) @@ -311,11 +299,11 @@ namespace MR stats_calculator.num_subjects(), "running " + str(num_permutations) + " permutations..."); - Processor processor (permutations, stats_calculator, enhancer, - empirical_enhanced_statistic, - default_enhanced_statistics, default_enhanced_statistics_neg, - perm_dist_pos, perm_dist_neg, - global_uncorrected_pvalue_count, global_uncorrected_pvalue_count_neg); + Processor processor (permutations, stats_calculator, enhancer, + empirical_enhanced_statistic, + default_enhanced_statistics, default_enhanced_statistics_neg, + perm_dist_pos, perm_dist_neg, + global_uncorrected_pvalue_count, global_uncorrected_pvalue_count_neg); auto threads = Thread::run (Thread::multi (processor), "permutation threads"); } diff --git a/src/stats/tfce.cpp b/src/stats/tfce.cpp new file mode 100644 index 0000000000..0dda55e0a3 --- /dev/null +++ b/src/stats/tfce.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "stats/tfce.h" + +namespace MR +{ + namespace Stats + { + namespace TFCE + { + + + + Enhancer::Enhancer (const Filter::Connector& connector, const value_type dh, const value_type E, const value_type H) : + connector (connector), + dh (dh), + E (E), + H (H) { } + + + + value_type Enhancer::operator() (const value_type max_stat, const vector_type& stats, vector_type& enhanced_stats) const + { + enhanced_stats = vector_type::Zero (stats.size()); + + for (value_type h = this->dh; h < max_stat; h += this->dh) { + std::vector clusters; + std::vector labels (enhanced_stats.size(), 0); + connector.run (clusters, labels, stats, h); + for (size_t i = 0; i < size_t(enhanced_stats.size()); ++i) + if (labels[i]) + enhanced_stats[i] += pow (clusters[labels[i]-1].size, this->E) * pow (h, this->H); + } + + return enhanced_stats.maxCoeff(); + } + + + + } + } +} diff --git a/src/stats/tfce.h b/src/stats/tfce.h index 1b7cb47c6f..7504ac74d6 100644 --- a/src/stats/tfce.h +++ b/src/stats/tfce.h @@ -12,12 +12,14 @@ * For more details, see www.mrtrix.org * */ + #ifndef __stats_tfce_h__ #define __stats_tfce_h__ -#include "math/stats/permutation.h" -#include "filter/connected_components.h" #include "thread_queue.h" +#include "filter/connected_components.h" +#include "math/stats/permutation.h" +#include "math/stats/typedefs.h" namespace MR { @@ -26,41 +28,29 @@ namespace MR namespace TFCE { - typedef float value_type; - typedef Eigen::Array vector_type; + + + typedef Math::Stats::value_type value_type; + typedef Math::Stats::vector_type vector_type; + /** \addtogroup Statistics @{ */ - class Enhancer { public: - Enhancer (const Filter::Connector& connector, const value_type dh, const value_type E, const value_type H) : - connector (connector), dh (dh), E (E), H (H) {} - - value_type operator() (const value_type max_stat, const vector_type& stats, - vector_type& enhanced_stats) const - { - enhanced_stats = vector_type::Zero (stats.size()); + Enhancer (const Filter::Connector& connector, const value_type dh, const value_type E, const value_type H); - for (value_type h = this->dh; h < max_stat; h += this->dh) { - std::vector clusters; - std::vector labels (enhanced_stats.size(), 0); - connector.run (clusters, labels, stats, h); - for (size_t i = 0; i < size_t(enhanced_stats.size()); ++i) - if (labels[i]) - enhanced_stats[i] += pow (clusters[labels[i]-1].size, this->E) * pow (h, this->H); - } - - return enhanced_stats.maxCoeff(); - } + value_type operator() (const value_type max_stat, const vector_type& stats, vector_type& enhanced_stats) const; protected: const Filter::Connector& connector; const value_type dh, E, H; }; - //! @} + + + } } } From 6b42385674638273ac9f685cdfd62a1ebeb6bacd Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 29 Jun 2016 20:04:53 +1000 Subject: [PATCH 014/723] Surface::Polygon: Add initializer list constructor --- src/surface/mesh.cpp | 2 +- src/surface/polygon.h | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/surface/mesh.cpp b/src/surface/mesh.cpp index 9445252402..1e004efa35 100644 --- a/src/surface/mesh.cpp +++ b/src/surface/mesh.cpp @@ -48,7 +48,7 @@ namespace MR throw Exception ("Input surface mesh file not in supported format"); } } - name = Path::banename (path); + name = Path::basename (path); } diff --git a/src/surface/polygon.h b/src/surface/polygon.h index a67689408e..82ba463a03 100644 --- a/src/surface/polygon.h +++ b/src/surface/polygon.h @@ -17,6 +17,7 @@ #define __surface_polygon_h__ #include +#include #include namespace MR @@ -46,6 +47,15 @@ namespace MR indices[i] = d[i]; } + template + Polygon (const std::initializer_list l) + { + assert (l.size() == vertices); + size_t counter = 0; + for (auto i = l.begin(); i != l.end(); ++i, ++counter) + indices[counter] = *i; + } + Polygon() { memset (indices, 0, vertices * sizeof (uint32_t)); From d24cb44a9723bb1b45407ee28f06d5b614d7daa6 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 1 Jul 2016 13:19:21 +1000 Subject: [PATCH 015/723] DWI::Directions::Set: Minor tweaks to QuickHull algorithm - Use default_type for calculations. - Use stack for hull expansion rather than sorted list. --- src/dwi/directions/set.cpp | 86 ++++++++++++++++++++++---------------- src/dwi/directions/set.h | 8 ++-- src/surface/mesh.cpp | 4 +- 3 files changed, 57 insertions(+), 41 deletions(-) diff --git a/src/dwi/directions/set.cpp b/src/dwi/directions/set.cpp index b936c71e00..6db81eb28c 100644 --- a/src/dwi/directions/set.cpp +++ b/src/dwi/directions/set.cpp @@ -20,7 +20,9 @@ #include #include +#include +#include "bitset.h" #include "math/rng.h" @@ -34,6 +36,8 @@ namespace MR { dir_t Set::get_min_linkage (const dir_t one, const dir_t two) const { + assert (one < size()); + assert (two < size()); if (one == two) return 0; @@ -100,9 +104,9 @@ namespace MR { class Vertex { public: Vertex (const Set& set, const dir_t index, const bool inverse) : - dir (set[index] * (inverse ? -1.0f : 1.0f)), + dir (set[index].cast() * (inverse ? -1.0 : 1.0)), index (index) { } - const Eigen::Vector3f dir; + const Eigen::Vector3 dir; const dir_t index; // Indexes the underlying direction set }; @@ -114,8 +118,8 @@ namespace MR { dist (std::max ( { vertices[one].dir.dot (normal), vertices[two].dir.dot (normal), vertices[three].dir.dot (normal) } ) ) { } bool includes (const dir_t i) const { return (indices[0] == i || indices[1] == i || indices[2] == i); } const std::array indices; // Indexes the vertices vector - const Eigen::Vector3f normal; - const float dist; + const Eigen::Vector3 normal; + const default_type dist; }; class PlaneComp @@ -134,7 +138,7 @@ namespace MR { } dir_t extremum_indices[3][2] = { {0, 0}, {0, 0}, {0, 0} }; - float extremum_values[3][2] = { {1.0f, -1.0f}, {1.0f, -1.0f}, {1.0f, -1.0f} }; + default_type extremum_values[3][2] = { {1.0f, -1.0f}, {1.0f, -1.0f}, {1.0f, -1.0f} }; for (size_t i = 0; i != vertices.size(); ++i) { for (size_t axis = 0; axis != 3; ++axis) { if (vertices[i].dir[axis] < extremum_values[axis][0]) { @@ -155,10 +159,10 @@ namespace MR { all_extrema.push_back (extremum_indices[axis][1]); } std::pair distant_pair; - float max_dist_sq = 0.0f; + default_type max_dist_sq = 0.0; for (dir_t i = 0; i != 6; ++i) { for (dir_t j = i + 1; j != 6; ++j) { - const float dist_sq = (vertices[all_extrema[j]].dir - vertices[all_extrema[i]].dir).squaredNorm(); + const default_type dist_sq = (vertices[all_extrema[j]].dir - vertices[all_extrema[i]].dir).squaredNorm(); if (dist_sq > max_dist_sq) { max_dist_sq = dist_sq; distant_pair = std::make_pair (i, j); @@ -169,10 +173,10 @@ namespace MR { // This forms the base line of the base triangle of the tetrahedon // Now from the remaining four extrema, find which one is farthest from this line dir_t third_point = 6; - float max_dist = 0.0f; + default_type max_dist = 0.0; for (dir_t i = 0; i != 6; ++i) { if (i != distant_pair.first && i != distant_pair.second) { - const float dist = (vertices[all_extrema[i]].dir - (vertices[all_extrema[distant_pair.first]].dir)).cross (vertices[all_extrema[i]].dir - vertices[all_extrema[distant_pair.second]].dir).norm() / (vertices[all_extrema[distant_pair.second]].dir - vertices[all_extrema[distant_pair.first]].dir).norm(); + const default_type dist = (vertices[all_extrema[i]].dir - (vertices[all_extrema[distant_pair.first]].dir)).cross (vertices[all_extrema[i]].dir - vertices[all_extrema[distant_pair.second]].dir).norm() / (vertices[all_extrema[distant_pair.second]].dir - vertices[all_extrema[distant_pair.first]].dir).norm(); if (dist > max_dist) { max_dist = dist; third_point = i; @@ -181,57 +185,66 @@ namespace MR { } assert (third_point != 6); - std::multiset planes; - planes.insert (Plane (vertices, all_extrema[distant_pair.first], all_extrema[distant_pair.second], all_extrema[third_point])); + // Does this have to be done in order? + // It appears not - however random deletion of entries _is_ required + //std::multiset planes; + std::list planes; + planes.push_back (Plane (vertices, all_extrema[distant_pair.first], all_extrema[distant_pair.second], all_extrema[third_point])); // Find the most distant point to this plane, and use it as the tip point of the tetrahedon const Plane base_plane = *planes.begin(); dir_t fourth_point = vertices.size(); - max_dist = 0.0f; + max_dist = 0.0; for (dir_t i = 0; i != vertices.size(); ++i) { // Use the reverse of the base plane normal - searching the other hemisphere - const float dist = vertices[i].dir.dot (-base_plane.normal); + const default_type dist = vertices[i].dir.dot (-base_plane.normal); if (dist > max_dist) { max_dist = dist; fourth_point = i; } } assert (fourth_point != vertices.size()); - planes.insert (Plane (vertices, base_plane.indices[0], fourth_point, base_plane.indices[1])); - planes.insert (Plane (vertices, base_plane.indices[1], fourth_point, base_plane.indices[2])); - planes.insert (Plane (vertices, base_plane.indices[2], fourth_point, base_plane.indices[0])); + planes.push_back (Plane (vertices, base_plane.indices[0], fourth_point, base_plane.indices[1])); + planes.push_back (Plane (vertices, base_plane.indices[1], fourth_point, base_plane.indices[2])); + planes.push_back (Plane (vertices, base_plane.indices[2], fourth_point, base_plane.indices[0])); std::vector hull; // Speedup: Only test those directions that have not yet been incorporated into any plane - std::list unassigned; - for (size_t i = 0; i != vertices.size(); ++i) { - if (!base_plane.includes (i) && fourth_point != i) - unassigned.push_back (i); - } + BitSet assigned (vertices.size()); + assigned[base_plane.indices[0]] = true; + assigned[base_plane.indices[1]] = true; + assigned[base_plane.indices[2]] = true; + assigned[fourth_point] = true; + size_t assigned_counter = 4; while (planes.size()) { - Plane current (*planes.begin()); - auto max_index = unassigned.end(); - float max_dist = current.dist; - for (auto d = unassigned.begin(); d != unassigned.end(); ++d) { - const float dist = vertices[*d].dir.dot (current.normal); - if (dist > max_dist) { - max_dist = dist; - max_index = d; + Plane current (planes.back()); + size_t max_index = vertices.size(); + default_type max_dist = current.dist; + for (size_t d = 0; d != vertices.size(); ++d) { + if (!assigned[d]) { + const default_type dist = vertices[d].dir.dot (current.normal); + if (dist > max_dist) { + max_dist = dist; + max_index = d; + } } } - if (max_index == unassigned.end()) { + if (max_index == vertices.size()) { hull.push_back (current); - planes.erase (planes.begin()); + planes.pop_back(); } else { // Identify all planes that this extremum point is above // More generally this would need to be constrained to only those faces adjacent to the // current plane, but because the data are on the sphere a complete search should be fine - std::vector::iterator> all_planes; - for (std::multiset::iterator p = planes.begin(); p != planes.end(); ++p) { - if (!p->includes (*max_index) && vertices[*max_index].dir.dot (p->normal) > p->dist) + + // TODO Using an alternative data structure, where both faces connected to each + // edge are stored and tracked, would speed this up considerably + std::vector< std::list::iterator > all_planes; + for (std::list::iterator p = planes.begin(); p != planes.end(); ++p) { + if (!p->includes (max_index) && vertices[max_index].dir.dot (p->normal) > p->dist) all_planes.push_back (p); } @@ -262,14 +275,15 @@ namespace MR { } for (auto& h : horizon) - planes.insert (Plane (vertices, h.first, h.second, *max_index)); + planes.push_back (Plane (vertices, h.first, h.second, max_index)); // Delete the used faces for (auto i : all_planes) planes.erase (i); // This point no longer needs to be tested - unassigned.erase (max_index); + assigned[max_index] = true; + ++assigned_counter; } } diff --git a/src/dwi/directions/set.h b/src/dwi/directions/set.h index 865a706b39..dee5c1540e 100644 --- a/src/dwi/directions/set.h +++ b/src/dwi/directions/set.h @@ -87,10 +87,12 @@ namespace MR { } size_t size () const { return unit_vectors.size(); } - const Eigen::Vector3f& get_dir (const size_t i) const { return unit_vectors[i]; } - const std::vector& get_adj_dirs (const size_t i) const { return adj_dirs[i]; } + const Eigen::Vector3f& get_dir (const size_t i) const { assert (i < size()); return unit_vectors[i]; } + const std::vector& get_adj_dirs (const size_t i) const { assert (i < size()); return adj_dirs[i]; } bool dirs_are_adjacent (const dir_t one, const dir_t two) const { + assert (one < size()); + assert (two < size()); for (const auto& i : adj_dirs[one]) { if (i == two) return true; @@ -101,7 +103,7 @@ namespace MR { dir_t get_min_linkage (const dir_t one, const dir_t two) const; const std::vector& get_dirs() const { return unit_vectors; } - const Eigen::Vector3f& operator[] (const size_t i) const { return unit_vectors[i]; } + const Eigen::Vector3f& operator[] (const size_t i) const { assert (i < size()); return unit_vectors[i]; } protected: diff --git a/src/surface/mesh.cpp b/src/surface/mesh.cpp index 1e004efa35..9f0b781852 100644 --- a/src/surface/mesh.cpp +++ b/src/surface/mesh.cpp @@ -527,7 +527,7 @@ namespace MR if (!in.good()) throw Exception ("Error reading FreeSurfer file: EOF reached"); for (int32_t i = 0; i != num_polygons; ++i) { - int32_t temp[3]; + std::array temp; for (size_t v = 0; v != 3; ++v) temp[v] = FreeSurfer::get_BE (in); triangles.push_back (Triangle (temp)); @@ -547,7 +547,7 @@ namespace MR vertices.push_back (Vertex (0.01 * temp[0], 0.01 * temp[1], 0.01 * temp[2])); } for (int32_t i = 0; i != num_polygons; ++i) { - int32_t temp[4]; + std::array temp; for (size_t v = 0; v != 4; ++v) temp[v] = FreeSurfer::get_int24_BE (in); quads.push_back (Quad (temp)); From 7c85a6819d9c1d103f6697645319a3ae51519edf Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 1 Jul 2016 21:45:56 +1000 Subject: [PATCH 016/723] More conversions to default_type Includes track mapping (excluding innermost track data processing loops), FOD FMLS segmenter, and DWI::Directions::Set class. --- cmd/fixel2tsf.cpp | 2 +- cmd/fixelcfestats.cpp | 4 +- cmd/fod2fixel.cpp | 6 +- src/dwi/directions/mask.cpp | 6 +- src/dwi/directions/set.cpp | 134 +++++++++--------- src/dwi/directions/set.h | 39 +++-- src/dwi/fmls.cpp | 20 +-- src/dwi/fmls.h | 44 +++--- src/dwi/tractography/SIFT/model_base.h | 49 ++++--- src/dwi/tractography/SIFT/output.h | 62 ++++---- src/dwi/tractography/SIFT2/tckfactor.cpp | 2 +- .../tractography/mapping/gaussian/mapper.cpp | 10 +- .../tractography/mapping/gaussian/mapper.h | 95 +++++++------ src/dwi/tractography/mapping/gaussian/voxel.h | 68 ++++----- src/dwi/tractography/mapping/mapper.cpp | 35 +++-- src/dwi/tractography/mapping/mapper.h | 77 +++++----- .../tractography/mapping/mapper_plugins.cpp | 8 +- src/dwi/tractography/mapping/mapper_plugins.h | 17 +-- src/dwi/tractography/mapping/voxel.cpp | 2 +- src/dwi/tractography/mapping/voxel.h | 127 +++++++++-------- src/dwi/tractography/mapping/writer.h | 66 ++++----- src/dwi/tractography/seeding/dynamic.cpp | 2 +- src/gui/dwi/renderer.cpp | 14 +- src/gui/dwi/renderer.h | 2 +- src/stats/cfe.cpp | 2 +- src/stats/cfe.h | 2 +- 26 files changed, 455 insertions(+), 440 deletions(-) diff --git a/cmd/fixel2tsf.cpp b/cmd/fixel2tsf.cpp index 48b45a4bfa..db34b722ba 100644 --- a/cmd/fixel2tsf.cpp +++ b/cmd/fixel2tsf.cpp @@ -105,7 +105,7 @@ void run () 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 (input_fixel); - Eigen::Vector3f dir = d->get_dir(); + Eigen::Vector3f dir = d->get_dir().cast(); dir.normalize(); float largest_dp = 0.0; int32_t closest_fixel_index = -1; diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 7fcb9d2524..71b01134e0 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -214,7 +214,7 @@ void run() { fixel_index_image.value() = directions.size(); int32_t fixel_count = 0; for (size_t f = 0; f != mask_fixel_image.value().size(); ++f, ++fixel_count) { - directions.push_back (mask_fixel_image.value()[f].dir); + directions.push_back (mask_fixel_image.value()[f].dir.cast()); const Eigen::Vector3 pos (mask_fixel_image.index(0), mask_fixel_image.index(1), mask_fixel_image.index(2)); positions.push_back (image_transform.voxel2scanner * pos); } @@ -326,7 +326,7 @@ void run() { value_type largest_dp = 0.0; int index_of_closest_fixel = -1; for (size_t f = 0; f != fixel.value().size(); ++f) { - const value_type dp = std::abs (directions[i].dot(fixel.value()[f].dir)); + const value_type dp = std::abs (directions[i].dot(fixel.value()[f].dir.cast())); if (dp > largest_dp) { largest_dp = dp; index_of_closest_fixel = f; diff --git a/cmd/fod2fixel.cpp b/cmd/fod2fixel.cpp index c51c140e9b..2d240a1bae 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -179,7 +179,7 @@ bool Segmented_FOD_receiver::operator() (const FOD_lobes& in) assign_pos_of (in.vox).to (*afd); afd->value().set_size (in.size()); for (size_t i = 0; i != in.size(); ++i) { - FixelMetric this_fixel (in[i].get_mean_dir(), in[i].get_integral(), in[i].get_integral()); + FixelMetric this_fixel (in[i].get_mean_dir().cast(), in[i].get_integral(), in[i].get_integral()); afd->value()[i] = this_fixel; } } @@ -188,7 +188,7 @@ bool Segmented_FOD_receiver::operator() (const FOD_lobes& in) assign_pos_of (in.vox).to (*peak); peak->value().set_size (in.size()); for (size_t i = 0; i != in.size(); ++i) { - FixelMetric this_fixel (in[i].get_peak_dir(), in[i].get_integral(), in[i].get_peak_value()); + FixelMetric this_fixel (in[i].get_peak_dir().cast(), in[i].get_integral(), in[i].get_peak_value()); peak->value()[i] = this_fixel; } } @@ -197,7 +197,7 @@ bool Segmented_FOD_receiver::operator() (const FOD_lobes& in) assign_pos_of (in.vox).to (*disp); disp->value().set_size (in.size()); for (size_t i = 0; i != in.size(); ++i) { - FixelMetric this_fixel (in[i].get_mean_dir(), in[i].get_integral(), in[i].get_integral() / in[i].get_peak_value()); + FixelMetric this_fixel (in[i].get_mean_dir().cast(), in[i].get_integral(), in[i].get_integral() / in[i].get_peak_value()); disp->value()[i] = this_fixel; } } diff --git a/src/dwi/directions/mask.cpp b/src/dwi/directions/mask.cpp index 3b8f90cd7c..ad4061adde 100644 --- a/src/dwi/directions/mask.cpp +++ b/src/dwi/directions/mask.cpp @@ -30,7 +30,7 @@ namespace MR { Mask temp (*this); for (size_t d = 0; d != size(); ++d) { if (!temp[d]) { - for (std::vector::const_iterator i = dirs->get_adj_dirs(d).begin(); i != dirs->get_adj_dirs(d).end(); ++i) + for (std::vector::const_iterator i = dirs->get_adj_dirs(d).begin(); i != dirs->get_adj_dirs(d).end(); ++i) reset (*i); } } @@ -45,7 +45,7 @@ namespace MR { Mask temp (*this); for (size_t d = 0; d != size(); ++d) { if (temp[d]) { - for (std::vector::const_iterator i = dirs->get_adj_dirs(d).begin(); i != dirs->get_adj_dirs(d).end(); ++i) + for (std::vector::const_iterator i = dirs->get_adj_dirs(d).begin(); i != dirs->get_adj_dirs(d).end(); ++i) set (*i); } } @@ -66,7 +66,7 @@ namespace MR { bool Mask::is_adjacent (const size_t d) const { - for (std::vector::const_iterator i = dirs->get_adj_dirs (d).begin(); i != dirs->get_adj_dirs (d).end(); ++i) { + for (std::vector::const_iterator i = dirs->get_adj_dirs (d).begin(); i != dirs->get_adj_dirs (d).end(); ++i) { if (test (*i)) return true; } diff --git a/src/dwi/directions/set.cpp b/src/dwi/directions/set.cpp index b936c71e00..c5aed518ca 100644 --- a/src/dwi/directions/set.cpp +++ b/src/dwi/directions/set.cpp @@ -32,19 +32,19 @@ namespace MR { - dir_t Set::get_min_linkage (const dir_t one, const dir_t two) const + index_type Set::get_min_linkage (const index_type one, const index_type two) const { if (one == two) return 0; std::vector processed (size(), 0); - std::vector to_expand; + std::vector to_expand; processed[one] = true; to_expand.push_back (one); - dir_t min_linkage = 0; + index_type min_linkage = 0; do { ++min_linkage; - std::vector next_to_expand; + std::vector next_to_expand; for (const auto& i : to_expand) { for (const auto& j : adj_dirs[i]) { if (j == two) { @@ -57,7 +57,7 @@ namespace MR { } std::swap (to_expand, next_to_expand); } while (1); - return std::numeric_limits::max(); + return std::numeric_limits::max(); } @@ -81,7 +81,7 @@ namespace MR { void Set::initialise_adjacency() { - adj_dirs.assign (size(), std::vector()); + adj_dirs.assign (size(), std::vector()); // New algorithm for determining direction adjacency // * Duplicate all directions to get a full spherical set @@ -99,23 +99,23 @@ namespace MR { class Vertex { public: - Vertex (const Set& set, const dir_t index, const bool inverse) : + Vertex (const Set& set, const index_type index, const bool inverse) : dir (set[index] * (inverse ? -1.0f : 1.0f)), index (index) { } - const Eigen::Vector3f dir; - const dir_t index; // Indexes the underlying direction set + const Eigen::Vector3 dir; + const index_type index; // Indexes the underlying direction set }; class Plane { public: - Plane (const std::vector& vertices, const dir_t one, const dir_t two, const dir_t three) : + Plane (const std::vector& vertices, const index_type one, const index_type two, const index_type three) : indices {{ one, two, three }}, normal (((vertices[two].dir-vertices[one].dir).cross (vertices[three].dir-vertices[two].dir)).normalized()), dist (std::max ( { vertices[one].dir.dot (normal), vertices[two].dir.dot (normal), vertices[three].dir.dot (normal) } ) ) { } - bool includes (const dir_t i) const { return (indices[0] == i || indices[1] == i || indices[2] == i); } - const std::array indices; // Indexes the vertices vector - const Eigen::Vector3f normal; - const float dist; + bool includes (const index_type i) const { return (indices[0] == i || indices[1] == i || indices[2] == i); } + const std::array indices; // Indexes the vertices vector + const Eigen::Vector3 normal; + const default_type dist; }; class PlaneComp @@ -128,13 +128,13 @@ namespace MR { std::vector vertices; // Generate antipodal vertices - for (dir_t i = 0; i != size(); ++i) { + for (index_type i = 0; i != size(); ++i) { vertices.push_back (Vertex (*this, i, false)); vertices.push_back (Vertex (*this, i, true)); } - dir_t extremum_indices[3][2] = { {0, 0}, {0, 0}, {0, 0} }; - float extremum_values[3][2] = { {1.0f, -1.0f}, {1.0f, -1.0f}, {1.0f, -1.0f} }; + index_type extremum_indices[3][2] = { {0, 0}, {0, 0}, {0, 0} }; + default_type extremum_values[3][2] = { {1.0, -1.0}, {1.0, -1.0}, {1.0, -1.0} }; for (size_t i = 0; i != vertices.size(); ++i) { for (size_t axis = 0; axis != 3; ++axis) { if (vertices[i].dir[axis] < extremum_values[axis][0]) { @@ -149,16 +149,16 @@ namespace MR { } // Find the two most distant points out of these six - std::vector all_extrema; + std::vector all_extrema; for (size_t axis = 0; axis != 3; ++axis) { all_extrema.push_back (extremum_indices[axis][0]); all_extrema.push_back (extremum_indices[axis][1]); } - std::pair distant_pair; - float max_dist_sq = 0.0f; - for (dir_t i = 0; i != 6; ++i) { - for (dir_t j = i + 1; j != 6; ++j) { - const float dist_sq = (vertices[all_extrema[j]].dir - vertices[all_extrema[i]].dir).squaredNorm(); + std::pair distant_pair; + default_type max_dist_sq = 0.0; + for (index_type i = 0; i != 6; ++i) { + for (index_type j = i + 1; j != 6; ++j) { + const default_type dist_sq = (vertices[all_extrema[j]].dir - vertices[all_extrema[i]].dir).squaredNorm(); if (dist_sq > max_dist_sq) { max_dist_sq = dist_sq; distant_pair = std::make_pair (i, j); @@ -168,11 +168,11 @@ namespace MR { // This forms the base line of the base triangle of the tetrahedon // Now from the remaining four extrema, find which one is farthest from this line - dir_t third_point = 6; - float max_dist = 0.0f; - for (dir_t i = 0; i != 6; ++i) { + index_type third_point = 6; + default_type max_dist = 0.0; + for (index_type i = 0; i != 6; ++i) { if (i != distant_pair.first && i != distant_pair.second) { - const float dist = (vertices[all_extrema[i]].dir - (vertices[all_extrema[distant_pair.first]].dir)).cross (vertices[all_extrema[i]].dir - vertices[all_extrema[distant_pair.second]].dir).norm() / (vertices[all_extrema[distant_pair.second]].dir - vertices[all_extrema[distant_pair.first]].dir).norm(); + const default_type dist = (vertices[all_extrema[i]].dir - (vertices[all_extrema[distant_pair.first]].dir)).cross (vertices[all_extrema[i]].dir - vertices[all_extrema[distant_pair.second]].dir).norm() / (vertices[all_extrema[distant_pair.second]].dir - vertices[all_extrema[distant_pair.first]].dir).norm(); if (dist > max_dist) { max_dist = dist; third_point = i; @@ -185,11 +185,11 @@ namespace MR { planes.insert (Plane (vertices, all_extrema[distant_pair.first], all_extrema[distant_pair.second], all_extrema[third_point])); // Find the most distant point to this plane, and use it as the tip point of the tetrahedon const Plane base_plane = *planes.begin(); - dir_t fourth_point = vertices.size(); - max_dist = 0.0f; - for (dir_t i = 0; i != vertices.size(); ++i) { + index_type fourth_point = vertices.size(); + max_dist = 0.0; + for (index_type i = 0; i != vertices.size(); ++i) { // Use the reverse of the base plane normal - searching the other hemisphere - const float dist = vertices[i].dir.dot (-base_plane.normal); + const default_type dist = vertices[i].dir.dot (-base_plane.normal); if (dist > max_dist) { max_dist = dist; fourth_point = i; @@ -212,9 +212,9 @@ namespace MR { while (planes.size()) { Plane current (*planes.begin()); auto max_index = unassigned.end(); - float max_dist = current.dist; + default_type max_dist = current.dist; for (auto d = unassigned.begin(); d != unassigned.end(); ++d) { - const float dist = vertices[*d].dir.dot (current.normal); + const default_type dist = vertices[*d].dir.dot (current.normal); if (dist > max_dist) { max_dist = dist; max_index = d; @@ -237,10 +237,10 @@ namespace MR { // Find the matching edges from multiple faces, and construct new triangles going up to the new point // Remove any shared edges; non-shared edges are the projection horizon - std::set> horizon; + std::set> horizon; for (auto& p : all_planes) { for (size_t edge_index = 0; edge_index != 3; ++edge_index) { - std::pair edge; + std::pair edge; switch (edge_index) { case 0: edge = std::make_pair (p->indices[0], p->indices[1]); break; case 1: edge = std::make_pair (p->indices[1], p->indices[2]); break; @@ -278,7 +278,7 @@ namespace MR { // Each of these three directions is adjacent // However: Each edge may have already been added from other triangles for (size_t edge = 0; edge != 6; ++edge) { - dir_t from = 0, to = 0; + index_type from = 0, to = 0; switch (edge) { case 0: from = vertices[current.indices[0]].index; to = vertices[current.indices[1]].index; break; case 1: from = vertices[current.indices[1]].index; to = vertices[current.indices[0]].index; break; @@ -317,16 +317,16 @@ namespace MR { - dir_t FastLookupSet::select_direction (const Eigen::Vector3f& p) const + index_type FastLookupSet::select_direction (const Eigen::Vector3& p) const { const size_t grid_index = dir2gridindex (p); - dir_t best_dir = grid_lookup[grid_index].front(); - float max_dp = std::abs (p.dot (get_dir (best_dir))); + index_type best_dir = grid_lookup[grid_index].front(); + default_type max_dp = std::abs (p.dot (get_dir (best_dir))); for (size_t i = 1; i != grid_lookup[grid_index].size(); ++i) { - const dir_t this_dir = (grid_lookup[grid_index])[i]; - const float this_dp = std::abs (p.dot (get_dir (this_dir))); + const index_type this_dir = (grid_lookup[grid_index])[i]; + const default_type this_dp = std::abs (p.dot (get_dir (this_dir))); if (this_dp > max_dp) { max_dp = this_dp; best_dir = this_dir; @@ -339,13 +339,13 @@ namespace MR { - dir_t FastLookupSet::select_direction_slow (const Eigen::Vector3f& p) const + index_type FastLookupSet::select_direction_slow (const Eigen::Vector3& p) const { - dir_t dir = 0; - float max_dot_product = std::abs (p.dot (unit_vectors[0])); + index_type dir = 0; + default_type max_dot_product = std::abs (p.dot (unit_vectors[0])); for (size_t i = 1; i != size(); ++i) { - const float this_dot_product = std::abs (p.dot (unit_vectors[i])); + const default_type this_dot_product = std::abs (p.dot (unit_vectors[i])); if (this_dot_product > max_dot_product) { max_dot_product = this_dot_product; dir = i; @@ -361,10 +361,10 @@ namespace MR { void FastLookupSet::initialise() { - double adj_dot_product_sum = 0.0; + default_type adj_dot_product_sum = 0.0; size_t adj_dot_product_count = 0; for (size_t i = 0; i != size(); ++i) { - for (std::vector::const_iterator j = adj_dirs[i].begin(); j != adj_dirs[i].end(); ++j) { + for (std::vector::const_iterator j = adj_dirs[i].begin(); j != adj_dirs[i].end(); ++j) { if (*j > i) { adj_dot_product_sum += std::abs (unit_vectors[i].dot (unit_vectors[*j])); ++adj_dot_product_count; @@ -372,20 +372,20 @@ namespace MR { } } - const float min_dp = adj_dot_product_sum / double(adj_dot_product_count); - const float max_angle_step = acos (min_dp); + const default_type min_dp = adj_dot_product_sum / default_type(adj_dot_product_count); + const default_type max_angle_step = acos (min_dp); num_az_grids = ceil (2.0 * Math::pi / max_angle_step); num_el_grids = ceil ( Math::pi / max_angle_step); total_num_angle_grids = num_az_grids * num_el_grids; - az_grid_step = 2.0 * Math::pi / float(num_az_grids - 1); - el_grid_step = Math::pi / float(num_el_grids - 1); + az_grid_step = 2.0 * Math::pi / default_type(num_az_grids - 1); + el_grid_step = Math::pi / default_type(num_el_grids - 1); az_begin = -Math::pi; el_begin = 0.0; - grid_lookup.assign (total_num_angle_grids, std::vector()); + grid_lookup.assign (total_num_angle_grids, std::vector()); for (size_t i = 0; i != size(); ++i) { const size_t grid_index = dir2gridindex (get_dir(i)); grid_lookup[grid_index].push_back (i); @@ -398,8 +398,8 @@ namespace MR { for (size_t point_index = 0; point_index != 4; ++point_index) { - float az = az_begin + (az_index * az_grid_step); - float el = el_begin + (el_index * el_grid_step); + default_type az = az_begin + (az_index * az_grid_step); + default_type el = el_begin + (el_index * el_grid_step); switch (point_index) { case 0: break; case 1: az += az_grid_step; break; @@ -407,10 +407,10 @@ namespace MR { case 3: el += el_grid_step; break; } - const Eigen::Vector3f p (cos(az) * sin(el), sin(az) * sin(el), cos (el)); - const dir_t nearest_dir = select_direction_slow (p); + const Eigen::Vector3 p (cos(az) * sin(el), sin(az) * sin(el), cos (el)); + const index_type nearest_dir = select_direction_slow (p); bool dir_present = false; - for (std::vector::const_iterator d = grid_lookup[i].begin(); !dir_present && d != grid_lookup[i].end(); ++d) + for (std::vector::const_iterator d = grid_lookup[i].begin(); !dir_present && d != grid_lookup[i].end(); ++d) dir_present = (*d == nearest_dir); if (!dir_present) grid_lookup[i].push_back (nearest_dir); @@ -420,16 +420,16 @@ namespace MR { } for (size_t grid_index = 0; grid_index != total_num_angle_grids; ++grid_index) { - std::vector& this_grid (grid_lookup[grid_index]); + std::vector& this_grid (grid_lookup[grid_index]); const size_t num_to_expand = this_grid.size(); for (size_t index_to_expand = 0; index_to_expand != num_to_expand; ++index_to_expand) { - const dir_t dir_to_expand = this_grid[index_to_expand]; - for (std::vector::const_iterator adj = get_adj_dirs(dir_to_expand).begin(); adj != get_adj_dirs(dir_to_expand).end(); ++adj) { + const index_type dir_to_expand = this_grid[index_to_expand]; + for (std::vector::const_iterator adj = get_adj_dirs(dir_to_expand).begin(); adj != get_adj_dirs(dir_to_expand).end(); ++adj) { // Size of lookup tables could potentially be reduced by being more prohibitive of adjacent direction inclusion in the lookup table for this grid bool is_present = false; - for (std::vector::const_iterator i = this_grid.begin(); !is_present && i != this_grid.end(); ++i) + for (std::vector::const_iterator i = this_grid.begin(); !is_present && i != this_grid.end(); ++i) is_present = (*i == *adj); if (!is_present) this_grid.push_back (*adj); @@ -443,11 +443,11 @@ namespace MR { - size_t FastLookupSet::dir2gridindex (const Eigen::Vector3f& p) const + size_t FastLookupSet::dir2gridindex (const Eigen::Vector3& p) const { - const float azimuth = atan2(p[1], p[0]); - const float elevation = acos (p[2]); + const default_type azimuth = atan2(p[1], p[0]); + const default_type elevation = acos (p[2]); const size_t azimuth_grid = std::floor (( azimuth - az_begin) / az_grid_step); const size_t elevation_grid = std::floor ((elevation - el_begin) / el_grid_step); @@ -462,17 +462,17 @@ namespace MR { void FastLookupSet::test_lookup() const { Math::RNG rng; - std::normal_distribution normal (0.0, 1.0); + std::normal_distribution<> normal (0.0, 1.0); size_t error_count = 0; const size_t checks = 1000000; for (size_t i = 0; i != checks; ++i) { - Eigen::Vector3f p (normal(rng), normal(rng), normal(rng)); + Eigen::Vector3 p (normal(rng), normal(rng), normal(rng)); p.normalize(); if (select_direction (p) != select_direction_slow (p)) ++error_count; } - const float error_rate = float(error_count) / float(checks); + const default_type error_rate = default_type(error_count) / default_type(checks); VAR (error_rate); } diff --git a/src/dwi/directions/set.h b/src/dwi/directions/set.h index 865a706b39..c32e4c7392 100644 --- a/src/dwi/directions/set.h +++ b/src/dwi/directions/set.h @@ -33,7 +33,7 @@ namespace MR { - typedef unsigned int dir_t; + typedef unsigned int index_type; @@ -87,10 +87,10 @@ namespace MR { } size_t size () const { return unit_vectors.size(); } - const Eigen::Vector3f& get_dir (const size_t i) const { return unit_vectors[i]; } - const std::vector& get_adj_dirs (const size_t i) const { return adj_dirs[i]; } + const Eigen::Vector3& get_dir (const size_t i) const { return unit_vectors[i]; } + const std::vector& get_adj_dirs (const size_t i) const { return adj_dirs[i]; } - bool dirs_are_adjacent (const dir_t one, const dir_t two) const { + bool dirs_are_adjacent (const index_type one, const index_type two) const { for (const auto& i : adj_dirs[one]) { if (i == two) return true; @@ -98,17 +98,16 @@ namespace MR { return false; } - dir_t get_min_linkage (const dir_t one, const dir_t two) const; + index_type get_min_linkage (const index_type one, const index_type two) const; - const std::vector& get_dirs() const { return unit_vectors; } - const Eigen::Vector3f& operator[] (const size_t i) const { return unit_vectors[i]; } + const std::vector& get_dirs() const { return unit_vectors; } + const Eigen::Vector3& operator[] (const size_t i) const { return unit_vectors[i]; } protected: - // TODO Change to double - std::vector unit_vectors; - std::vector< std::vector > adj_dirs; // Note: not self-inclusive + std::vector unit_vectors; + std::vector< std::vector > adj_dirs; // Note: not self-inclusive private: @@ -135,14 +134,14 @@ namespace MR { unit_vectors.resize (in.rows()); if (in.cols() == 2) { for (size_t i = 0; i != size(); ++i) { - const float azimuth = in(i, 0); - const float elevation = in(i, 1); - const float sin_elevation = std::sin (elevation); + const default_type azimuth = in(i, 0); + const default_type elevation = in(i, 1); + const default_type sin_elevation = std::sin (elevation); unit_vectors[i] = { std::cos (azimuth) * sin_elevation, std::sin (azimuth) * sin_elevation, std::cos (elevation) }; } } else if (in.cols() == 3) { for (size_t i = 0; i != size(); ++i) - unit_vectors[i] = { float(in(i,0)), float(in(i,1)), float(in(i,2)) }; + unit_vectors[i] = { default_type(in(i,0)), default_type(in(i,1)), default_type(in(i,2)) }; } else { assert (0); } @@ -182,24 +181,24 @@ namespace MR { az_begin (that.az_begin), el_begin (that.el_begin) { } - dir_t select_direction (const Eigen::Vector3f&) const; + index_type select_direction (const Eigen::Vector3&) const; private: - std::vector< std::vector > grid_lookup; + std::vector< std::vector > grid_lookup; unsigned int num_az_grids, num_el_grids, total_num_angle_grids; - float az_grid_step, el_grid_step; - float az_begin, el_begin; + default_type az_grid_step, el_grid_step; + default_type az_begin, el_begin; FastLookupSet (); - dir_t select_direction_slow (const Eigen::Vector3f&) const; + index_type select_direction_slow (const Eigen::Vector3&) const; void initialise(); - size_t dir2gridindex (const Eigen::Vector3f&) const; + size_t dir2gridindex (const Eigen::Vector3&) const; void test_lookup() const; diff --git a/src/dwi/fmls.cpp b/src/dwi/fmls.cpp index 83eb107649..3af7a81dfd 100644 --- a/src/dwi/fmls.cpp +++ b/src/dwi/fmls.cpp @@ -34,19 +34,19 @@ namespace MR { "threshold the ratio between the integral of a positive FOD lobe, and the integral of the largest negative lobe. " "Any lobe that fails to exceed the integral dictated by this ratio will be discarded. " "Default: " + str(FMLS_RATIO_TO_NEGATIVE_LOBE_INTEGRAL_DEFAULT, 2) + ".") - + App::Argument ("value").type_float (0.0, 1e6) + + App::Argument ("value").type_float (0.0) + App::Option ("fmls_ratio_peak_to_mean_neg", "threshold the ratio between the peak amplitude of a positive FOD lobe, and the mean peak amplitude of all negative lobes. " "Any lobe that fails to exceed the peak amplitude dictated by this ratio will be discarded. " "Default: " + str(FMLS_RATIO_TO_NEGATIVE_LOBE_MEAN_PEAK_DEFAULT, 2) + ".") - + App::Argument ("value").type_float (0.0, 1e6) + + App::Argument ("value").type_float (0.0) + App::Option ("fmls_peak_value", "threshold the raw peak amplitude of positive FOD lobes. " "Any lobe for which the peak amplitude is smaller than this threshold will be discarded. " "Default: " + str(FMLS_PEAK_VALUE_THRESHOLD, 2) + ".") - + App::Argument ("value").type_float (0.0, 1e6) + + App::Argument ("value").type_float (0.0) + App::Option ("fmls_no_thresholds", "disable all FOD lobe thresholding; every lobe with a positive FOD amplitude will be retained.") @@ -158,7 +158,7 @@ namespace MR { Eigen::Matrix values (dirs.size()); transform->SH2A (values, in); - typedef std::multimap map_type; + typedef std::multimap map_type; map_type data_in_order; for (size_t i = 0; i != size_t(values.size()); ++i) data_in_order.insert (std::make_pair (values[i], i)); @@ -166,7 +166,7 @@ namespace MR { if (data_in_order.begin()->first <= 0.0) return true; - std::vector< std::pair > retrospective_assignments; + std::vector< std::pair > retrospective_assignments; for (const auto& i : data_in_order) { @@ -255,9 +255,9 @@ namespace MR { if (i->is_negative() || i->get_peak_value() < std::max (min_peak_amp, peak_value_threshold) || i->get_integral() < min_integral) { i = out.erase (i); } else { - const dir_t peak_bin (i->get_peak_dir_bin()); + const index_type peak_bin (i->get_peak_dir_bin()); auto newton_peak = dirs.get_dir (peak_bin); - float new_peak_value = Math::SH::get_peak (in, lmax, newton_peak, &(*precomputer)); + const default_type new_peak_value = Math::SH::get_peak (in, lmax, newton_peak, &(*precomputer)); if (std::isfinite (new_peak_value) && new_peak_value > i->get_peak_value() && std::isfinite (newton_peak[0])) i->revise_peak (newton_peak, new_peak_value); i->finalise(); @@ -290,15 +290,15 @@ namespace MR { NON_POD_VLA (new_assignments, std::vector, dirs.size()); while (!processed.full()) { - for (dir_t dir = 0; dir != dirs.size(); ++dir) { + for (index_type dir = 0; dir != dirs.size(); ++dir) { if (!processed[dir]) { - for (std::vector::const_iterator neighbour = dirs.get_adj_dirs (dir).begin(); neighbour != dirs.get_adj_dirs (dir).end(); ++neighbour) { + for (std::vector::const_iterator neighbour = dirs.get_adj_dirs (dir).begin(); neighbour != dirs.get_adj_dirs (dir).end(); ++neighbour) { if (processed[*neighbour]) new_assignments[dir].push_back (out.lut[*neighbour]); } } } - for (dir_t dir = 0; dir != dirs.size(); ++dir) { + for (index_type dir = 0; dir != dirs.size(); ++dir) { if (new_assignments[dir].size() == 1) { out.lut[dir] = new_assignments[dir].front(); diff --git a/src/dwi/fmls.h b/src/dwi/fmls.h index d8ed8a1177..1594cdd970 100644 --- a/src/dwi/fmls.h +++ b/src/dwi/fmls.h @@ -54,7 +54,7 @@ namespace MR using DWI::Directions::Mask; - using DWI::Directions::dir_t; + using DWI::Directions::index_type; class Segmenter; @@ -67,9 +67,9 @@ namespace MR class FOD_lobe { public: - FOD_lobe (const DWI::Directions::Set& dirs, const dir_t seed, const default_type value) : + FOD_lobe (const DWI::Directions::Set& dirs, const index_type seed, const default_type value) : mask (dirs), - values (dirs.size(), 0.0), + values (Eigen::Array::Zero (dirs.size())), peak_dir_bin (seed), peak_value (std::abs (value)), peak_dir (dirs.get_dir (seed)), @@ -85,25 +85,25 @@ namespace MR // assigned to any other lobe in the voxel FOD_lobe (const DWI::Directions::Mask& i) : mask (i), - values (i.size(), 0.0), + values (Eigen::Array::Zero (i.size())), peak_dir_bin (i.size()), peak_value (0.0), integral (0.0), neg (false) { } - void add (const dir_t bin, const default_type value) + void add (const index_type bin, const default_type value) { assert ((value <= 0.0 && neg) || (value >= 0.0 && !neg)); mask[bin] = true; values[bin] = value; - const Eigen::Vector3f& dir = mask.get_dirs()[bin]; - const float multiplier = (peak_dir.dot (dir)) > 0.0 ? 1.0 : -1.0; + const Eigen::Vector3& dir = mask.get_dirs()[bin]; + const default_type multiplier = (peak_dir.dot (dir)) > 0.0 ? 1.0 : -1.0; mean_dir += dir * multiplier * value; integral += std::abs (value); } - void revise_peak (const Eigen::Vector3f& real_peak, const float value) + void revise_peak (const Eigen::Vector3& real_peak, const default_type value) { assert (!neg); peak_dir = real_peak; @@ -121,7 +121,7 @@ namespace MR void finalise() { // 2pi == solid angle of halfsphere in steradians - integral *= 2.0 * Math::pi / float(mask.size()); + integral *= 2.0 * Math::pi / default_type(mask.size()); // This is calculated as the lobe is built, just needs to be set to unit length mean_dir.normalize(); } @@ -137,29 +137,29 @@ namespace MR peak_value = that.peak_value; peak_dir = that.peak_dir; } - const float multiplier = (mean_dir.dot (that.mean_dir)) > 0.0 ? 1.0 : -1.0; + const default_type multiplier = (mean_dir.dot (that.mean_dir)) > 0.0 ? 1.0 : -1.0; mean_dir += that.mean_dir * that.integral * multiplier; integral += that.integral; } const DWI::Directions::Mask& get_mask() const { return mask; } - const std::vector& get_values() const { return values; } - dir_t get_peak_dir_bin() const { return peak_dir_bin; } - float get_peak_value() const { return peak_value; } - const Eigen::Vector3f& get_peak_dir() const { return peak_dir; } - const Eigen::Vector3f& get_mean_dir() const { return mean_dir; } - float get_integral() const { return integral; } + const Eigen::Array& get_values() const { return values; } + index_type get_peak_dir_bin() const { return peak_dir_bin; } + default_type get_peak_value() const { return peak_value; } + const Eigen::Vector3& get_peak_dir() const { return peak_dir; } + const Eigen::Vector3& get_mean_dir() const { return mean_dir; } + default_type get_integral() const { return integral; } bool is_negative() const { return neg; } private: DWI::Directions::Mask mask; - std::vector values; - dir_t peak_dir_bin; - float peak_value; - Eigen::Vector3f peak_dir; - Eigen::Vector3f mean_dir; - float integral; + Eigen::Array values; + index_type peak_dir_bin; + default_type peak_value; + Eigen::Vector3 peak_dir; + Eigen::Vector3 mean_dir; + default_type integral; bool neg; }; diff --git a/src/dwi/tractography/SIFT/model_base.h b/src/dwi/tractography/SIFT/model_base.h index 57d22507c2..b28ee51958 100644 --- a/src/dwi/tractography/SIFT/model_base.h +++ b/src/dwi/tractography/SIFT/model_base.h @@ -69,13 +69,13 @@ namespace MR weight (0.0), dir () { } - FixelBase (const double amp) : + FixelBase (const default_type amp) : FOD (amp), TD (0.0), weight (1.0), dir () { } - FixelBase (const double amp, const Eigen::Vector3f& d) : + FixelBase (const default_type amp, const Eigen::Vector3& d) : FOD (amp), TD (0.0), weight (1.0), @@ -89,27 +89,26 @@ namespace MR FixelBase (const FixelBase&) = default; - double get_FOD() const { return FOD; } - double get_TD() const { return TD; } - float get_weight() const { return weight; } - const Eigen::Vector3f& get_dir() const { return dir; } + default_type get_FOD() const { return FOD; } + default_type get_TD() const { return TD; } + default_type get_weight() const { return weight; } + const Eigen::Vector3& get_dir() const { return dir; } - void scale_FOD (const float factor) { FOD *= factor; } - void set_weight (const float w) { weight = w; } - FixelBase& operator+= (const double length) { TD += length; return *this; } - void clear_TD () { TD = 0.0; } + void scale_FOD (const default_type factor) { FOD *= factor; } + void set_weight (const default_type w) { weight = w; } + FixelBase& operator+= (const default_type length) { TD += length; return *this; } - double get_diff (const double mu) const { return ((TD * mu) - FOD); } - double get_cost (const double mu) const { return get_cost_unweighted (mu) * weight; } + void clear_TD() { TD = 0.0; } + + default_type get_diff (const default_type mu) const { return ((TD * mu) - FOD); } + default_type get_cost (const default_type mu) const { return get_cost_unweighted (mu) * weight; } protected: - double FOD; - double TD; - float weight; - Eigen::Vector3f dir; + default_type FOD, TD, weight; + Eigen::Vector3 dir; - double get_cost_unweighted (const double mu) const { return Math::pow2 (get_diff (mu)); } + default_type get_cost_unweighted (const default_type mu) const { return Math::pow2 (get_diff (mu)); } }; @@ -152,9 +151,9 @@ namespace MR virtual bool operator() (const FMLS::FOD_lobes& in); virtual bool operator() (const Mapping::SetDixel& in); - double calc_cost_function() const; + default_type calc_cost_function() const; - double mu() const { return FOD_sum / TD_sum; } + default_type mu() const { return FOD_sum / TD_sum; } bool have_act_data() const { return act_5tt.valid(); } void output_proc_mask (const std::string&); @@ -170,7 +169,7 @@ namespace MR using Mapping::Fixel_TD_map::dirs; Image act_5tt, proc_mask; - double FOD_sum, TD_sum; + default_type FOD_sum, TD_sum; bool have_null_lobes; // The definitions of these functions are located in dwi/tractography/SIFT/output.h @@ -221,7 +220,7 @@ namespace MR FOD_sum = 0.0; for (auto l = Loop(v) (v, act_5tt); l; ++l) { Tractography::ACT::Tissues tissues (act_5tt); - const float multiplier = 1.0 - tissues.get_cgm() - (0.5 * tissues.get_sgm()); // Heuristic + const default_type multiplier = 1.0 - tissues.get_cgm() - (0.5 * tissues.get_sgm()); // Heuristic for (typename Fixel_map::Iterator i = begin(v); i; ++i) { i().scale_FOD (multiplier); FOD_sum += i().get_weight() * i().get_FOD(); @@ -286,7 +285,7 @@ namespace MR template bool ModelBase::operator() (const Mapping::SetDixel& in) { - float total_contribution = 0.0; + default_type total_contribution = 0.0; for (Mapping::SetDixel::const_iterator i = in.begin(); i != in.end(); ++i) { const size_t fixel_index = Mapping::Fixel_TD_map::dixel2fixel (*i); if (fixel_index) { @@ -302,10 +301,10 @@ namespace MR template - double ModelBase::calc_cost_function() const + default_type ModelBase::calc_cost_function() const { - const double current_mu = mu(); - double cost = 0.0; + const default_type current_mu = mu(); + default_type cost = 0.0; for (auto i = fixels.cbegin()+1; i != fixels.end(); ++i) cost += i->get_cost (current_mu); return cost; diff --git a/src/dwi/tractography/SIFT/output.h b/src/dwi/tractography/SIFT/output.h index 9bf481c9fb..ca20c42c31 100644 --- a/src/dwi/tractography/SIFT/output.h +++ b/src/dwi/tractography/SIFT/output.h @@ -53,7 +53,7 @@ namespace MR VoxelAccessor v (accessor()); for (auto l = Loop(out) (out, v); l; ++l) { if (v.value()) { - float value = 0.0; + default_type value = 0.0; for (typename Fixel_map::ConstIterator i = begin (v); i; ++i) value += i().get_FOD(); out.value() = value; @@ -68,7 +68,7 @@ namespace MR { const size_t L = 8; const size_t N = Math::SH::NforL (L); - Math::SH::aPSF aPSF (L); + Math::SH::aPSF aPSF (L); Header H_sh (Fixel_map::header()); H_sh.set_ndim (4); H_sh.size(3) = N; @@ -77,11 +77,10 @@ namespace MR VoxelAccessor v (accessor()); for (auto l = Loop (0, 3) (out, v); l; ++l) { if (v.value()) { - Eigen::VectorXf sum; - sum.resize (N, 0.0); + Eigen::Matrix sum = Eigen::Matrix::Zero (N); for (typename Fixel_map::ConstIterator i = begin (v); i; ++i) { if (i().get_FOD()) { - Eigen::VectorXf this_lobe; + Eigen::Matrix this_lobe; aPSF (this_lobe, i().get_dir()); for (size_t c = 0; c != N; ++c) sum[c] += i().get_FOD() * this_lobe[c]; @@ -112,7 +111,7 @@ namespace MR out.value().set_size ((*v.value()).num_fixels()); size_t index = 0; for (typename Fixel_map::ConstIterator iter = begin (v); iter; ++iter, ++index) { - FixelMetric fixel (iter().get_dir(), iter().get_FOD(), iter().get_FOD()); + FixelMetric fixel (iter().get_dir().template cast(), iter().get_FOD(), iter().get_FOD()); out.value()[index] = fixel; } } @@ -122,17 +121,17 @@ namespace MR template void ModelBase::output_tdi (const std::string& path) const { - const double current_mu = mu(); + const default_type current_mu = mu(); auto out = Image::create (path, Fixel_map::header()); VoxelAccessor v (accessor()); for (auto l = Loop (out) (out, v); l; ++l) { if (v.value()) { - float value = 0.0; + default_type value = 0.0; for (typename Fixel_map::ConstIterator i = begin (v); i; ++i) value += i().get_TD(); out.value() = value * current_mu; } else { - out.value() = NAN; + out.value() = NaN; } } } @@ -140,19 +139,19 @@ namespace MR template void ModelBase::output_tdi_null_lobes (const std::string& path) const { - const double current_mu = mu(); + const default_type current_mu = mu(); auto out = Image::create (path, Fixel_map::header()); VoxelAccessor v (accessor()); for (auto l = Loop (out) (out, v); l; ++l) { if (v.value()) { - float value = 0.0; + default_type value = 0.0; for (typename Fixel_map::ConstIterator i = begin (v); i; ++i) { if (!i().get_FOD()) value += i().get_TD(); } out.value() = value * current_mu; } else { - out.value() = NAN; + out.value() = NaN; } } } @@ -160,10 +159,10 @@ namespace MR template void ModelBase::output_tdi_sh (const std::string& path) const { - const double current_mu = mu(); + const default_type current_mu = mu(); const size_t L = 8; const size_t N = Math::SH::NforL (L); - Math::SH::aPSF aPSF (L); + Math::SH::aPSF aPSF (L); Header H_sh (Fixel_map::header()); H_sh.set_ndim (4); H_sh.size(3) = N; @@ -172,11 +171,10 @@ namespace MR VoxelAccessor v (accessor()); for (auto l = Loop (out) (out, v); l; ++l) { if (v.value()) { - Eigen::VectorXf sum; - sum.resize (N, 0.0); + Eigen::Matrix sum = Eigen::Matrix::Zero (N); for (typename Fixel_map::ConstIterator i = begin (v); i; ++i) { if (i().get_FOD()) { - Eigen::VectorXf this_lobe; + Eigen::Matrix this_lobe; aPSF (this_lobe, i().get_dir()); for (size_t c = 0; c != N; ++c) sum[c] += i().get_TD() * this_lobe[c]; @@ -186,7 +184,7 @@ namespace MR } } else { for (auto l = Loop (3) (out); l; ++l) - out.value() = NAN; + out.value() = NaN; } } } @@ -195,7 +193,7 @@ namespace MR void ModelBase::output_tdi_fixel (const std::string& path) const { using Sparse::FixelMetric; - const double current_mu = mu(); + const default_type current_mu = mu(); Header H_fixel (Fixel_map::header()); H_fixel.datatype() = DataType::UInt64; H_fixel.datatype().set_byte_order_native(); @@ -208,7 +206,7 @@ namespace MR out.value().set_size ((*v.value()).num_fixels()); size_t index = 0; for (typename Fixel_map::ConstIterator iter = begin (v); iter; ++iter, ++index) { - FixelMetric fixel (iter().get_dir(), iter().get_FOD(), current_mu * iter().get_TD()); + FixelMetric fixel (iter().get_dir().template cast(), iter().get_FOD(), current_mu * iter().get_TD()); out.value()[index] = fixel; } } @@ -218,16 +216,16 @@ namespace MR template void ModelBase::output_error_images (const std::string& max_abs_diff_path, const std::string& diff_path, const std::string& cost_path) const { - const double current_mu = mu(); + const default_type current_mu = mu(); auto out_max_abs_diff = Image::create (max_abs_diff_path, Fixel_map::header()); auto out_diff = Image::create (diff_path, Fixel_map::header()); auto out_cost = Image::create (cost_path, Fixel_map::header()); VoxelAccessor v (accessor()); for (auto l = Loop (v) (v, out_max_abs_diff, out_diff, out_cost); l; ++l) { if (v.value()) { - double max_abs_diff = 0.0, diff = 0.0, cost = 0.0; + default_type max_abs_diff = 0.0, diff = 0.0, cost = 0.0; for (typename Fixel_map::ConstIterator i = begin (v); i; ++i) { - const double this_diff = i().get_diff (current_mu); + const default_type this_diff = i().get_diff (current_mu); max_abs_diff = std::max (max_abs_diff, std::abs (this_diff)); diff += this_diff; cost += i().get_cost (current_mu) * i().get_weight(); @@ -236,9 +234,9 @@ namespace MR out_diff.value() = diff; out_cost.value() = cost; } else { - out_max_abs_diff.value() = NAN; - out_diff.value() = NAN; - out_cost.value() = NAN; + out_max_abs_diff.value() = NaN; + out_diff.value() = NaN; + out_cost.value() = NaN; } } } @@ -247,7 +245,7 @@ namespace MR void ModelBase::output_error_fixel_images (const std::string& diff_path, const std::string& cost_path) const { using Sparse::FixelMetric; - const double current_mu = mu(); + const default_type current_mu = mu(); Header H_fixel (Fixel_map::header()); H_fixel.datatype() = DataType::UInt64; H_fixel.datatype().set_byte_order_native(); @@ -262,9 +260,9 @@ namespace MR out_cost.value().set_size ((*v.value()).num_fixels()); size_t index = 0; for (typename Fixel_map::ConstIterator iter = begin (v); iter; ++iter, ++index) { - FixelMetric fixel_diff (iter().get_dir(), iter().get_FOD(), iter().get_diff (current_mu)); + FixelMetric fixel_diff (iter().get_dir().template cast(), iter().get_FOD(), iter().get_diff (current_mu)); out_diff.value()[index] = fixel_diff; - FixelMetric fixel_cost (iter().get_dir(), iter().get_FOD(), iter().get_cost (current_mu)); + FixelMetric fixel_cost (iter().get_dir().template cast(), iter().get_FOD(), iter().get_cost (current_mu)); out_cost.value()[index] = fixel_cost; } } @@ -275,7 +273,7 @@ namespace MR void ModelBase::output_scatterplot (const std::string& path) const { File::OFStream out (path, std::ios_base::out | std::ios_base::trunc); - const double current_mu = mu(); + const default_type current_mu = mu(); out << "Fibre density,Track density (unscaled),Track density (scaled),Weight,\n"; for (typename std::vector::const_iterator i = fixels.begin(); i != fixels.end(); ++i) out << str (i->get_FOD()) << "," << str (i->get_TD()) << "," << str (i->get_TD() * current_mu) << "," << str (i->get_weight()) << ",\n"; @@ -308,7 +306,7 @@ namespace MR for (auto l = Loop (v) (v, out_count, out_amps, v); l; ++l) { if (v.value()) { uint8_t count = 0; - float sum = 0.0; + default_type sum = 0.0; for (typename Fixel_map::ConstIterator i = begin (v); i; ++i) { if (!i().get_TD()) { ++count; @@ -319,7 +317,7 @@ namespace MR out_amps .value() = sum; } else { out_count.value() = 0; - out_amps .value() = NAN; + out_amps .value() = NaN; } } } diff --git a/src/dwi/tractography/SIFT2/tckfactor.cpp b/src/dwi/tractography/SIFT2/tckfactor.cpp index 3ac1d25e3c..628482675c 100644 --- a/src/dwi/tractography/SIFT2/tckfactor.cpp +++ b/src/dwi/tractography/SIFT2/tckfactor.cpp @@ -414,7 +414,7 @@ namespace MR { size_t index = 0; for (typename Fixel_map::ConstIterator iter = begin (v); iter; ++iter, ++index) { const size_t fixel_index = size_t(iter); - FixelMetric fixel_metric (iter().get_dir(), iter().get_FOD(), iter().get_count()); + FixelMetric fixel_metric (iter().get_dir().cast(), iter().get_FOD(), iter().get_count()); count_image .value()[index] = fixel_metric; fixel_metric.value = mins[fixel_index]; min_image .value()[index] = fixel_metric; diff --git a/src/dwi/tractography/mapping/gaussian/mapper.cpp b/src/dwi/tractography/mapping/gaussian/mapper.cpp index b54ccc6dac..5c83253137 100644 --- a/src/dwi/tractography/mapping/gaussian/mapper.cpp +++ b/src/dwi/tractography/mapping/gaussian/mapper.cpp @@ -41,22 +41,22 @@ namespace MR { void TrackMapper::gaussian_smooth_factors (const Streamline<>& tck) const { - std::vector unsmoothed (factors); + std::vector unsmoothed (factors); for (size_t i = 0; i != unsmoothed.size(); ++i) { - double sum = 0.0, norm = 0.0; + default_type sum = 0.0, norm = 0.0; if (std::isfinite (unsmoothed[i])) { sum = unsmoothed[i]; norm = 1.0; // Gaussian is unnormalised -> e^0 = 1 } - float distance = 0.0; + default_type distance = 0.0; for (size_t j = i; j--; ) { // Decrement AFTER null test, so loop runs with j = 0 distance += (tck[j] - tck[j+1]).norm(); if (std::isfinite (unsmoothed[j])) { - const float this_weight = exp (-distance * distance / gaussian_denominator); + const default_type this_weight = exp (-distance * distance / gaussian_denominator); norm += this_weight; sum += this_weight * unsmoothed[j]; } @@ -65,7 +65,7 @@ namespace MR { for (size_t j = i + 1; j < unsmoothed.size(); ++j) { distance += (tck[j] - tck[j-1]).norm(); if (std::isfinite (unsmoothed[j])) { - const float this_weight = exp (-distance * distance / gaussian_denominator); + const default_type this_weight = exp (-distance * distance / gaussian_denominator); norm += this_weight; sum += this_weight * unsmoothed[j]; } diff --git a/src/dwi/tractography/mapping/gaussian/mapper.h b/src/dwi/tractography/mapping/gaussian/mapper.h index cccab34944..ddfd814b9b 100644 --- a/src/dwi/tractography/mapping/gaussian/mapper.h +++ b/src/dwi/tractography/mapping/gaussian/mapper.h @@ -51,11 +51,11 @@ namespace MR { ~TrackMapper() { } - void set_gaussian_FWHM (const float FWHM) + void set_gaussian_FWHM (const default_type FWHM) { if (track_statistic != GAUSSIAN) throw Exception ("Cannot set Gaussian FWHM unless the track statistic is Gaussian"); - const float theta = FWHM / (2.0 * std::sqrt (2.0 * std::log (2.0))); + const default_type theta = FWHM / (2.0 * std::sqrt (2.0 * std::log (2.0))); gaussian_denominator = 2.0 * Math::pow2 (theta); } @@ -88,8 +88,8 @@ namespace MR { protected: - float gaussian_denominator; - void gaussian_smooth_factors (const Streamline<>&) const; + default_type gaussian_denominator; + void gaussian_smooth_factors (const Streamline<>&) const; // Overload corresponding functions in TrackMapperTWI void set_factor (const Streamline<>& tck, SetVoxelExtras& out) const; @@ -101,14 +101,14 @@ namespace MR { template void voxelise_precise (const Streamline<>&, Cont&) const; template void voxelise_ends (const Streamline<>&, Cont&) const; - inline void add_to_set (SetVoxel& , const Eigen::Vector3i&, const Eigen::Vector3f&, const float, const float) const; - inline void add_to_set (SetVoxelDEC&, const Eigen::Vector3i&, const Eigen::Vector3f&, const float, const float) const; - inline void add_to_set (SetDixel& , const Eigen::Vector3i&, const Eigen::Vector3f&, const float, const float) const; - inline void add_to_set (SetVoxelTOD&, const Eigen::Vector3i&, const Eigen::Vector3f&, const float, const float) const; + inline void add_to_set (SetVoxel& , const Eigen::Vector3i&, const Eigen::Vector3&, const default_type, const default_type) const; + inline void add_to_set (SetVoxelDEC&, const Eigen::Vector3i&, const Eigen::Vector3&, const default_type, const default_type) const; + inline void add_to_set (SetDixel& , const Eigen::Vector3i&, const Eigen::Vector3&, const default_type, const default_type) const; + inline void add_to_set (SetVoxelTOD&, const Eigen::Vector3i&, const Eigen::Vector3&, const default_type, const default_type) const; // Convenience function to convert from streamline position index to a linear-interpolated // factor value (TrackMapperTWI member field factors[] only contains one entry per pre-upsampled point) - inline float tck_index_to_factor (const size_t) const; + inline default_type tck_index_to_factor (const size_t) const; }; @@ -126,17 +126,17 @@ namespace MR { for (size_t i = 0; i != last; ++i) { vox = round (scanner2voxel * tck[i]); if (check (vox, info)) { - const Eigen::Vector3f dir ((tck[i+1] - tck[prev]).normalized()); - const float factor = tck_index_to_factor (i); - add_to_set (output, vox, dir, 1.0f, factor); + const Eigen::Vector3 dir ((tck[i+1] - tck[prev]).cast().normalized()); + const default_type factor = tck_index_to_factor (i); + add_to_set (output, vox, dir, 1.0, factor); } prev = i; } vox = round (scanner2voxel * tck[last]); if (check (vox, info)) { - const Eigen::Vector3f dir ((tck[last] - tck[prev]).normalized()); - const float factor = tck_index_to_factor (last); + const Eigen::Vector3 dir ((tck[last] - tck[prev]).cast().normalized()); + const default_type factor = tck_index_to_factor (last); add_to_set (output, vox, dir, 1.0f, factor); } @@ -153,30 +153,31 @@ namespace MR { template void TrackMapper::voxelise_precise (const Streamline<>& tck, Cont& out) const { - typedef Eigen::Vector3f PointF; + typedef Streamline<>::point_type point_type; + typedef Streamline<>::value_type value_type; - static const float accuracy = Math::pow2 (0.005 * std::min (info.spacing (0), std::min (info.spacing (1), info.spacing (2)))); + static const default_type accuracy = Math::pow2 (0.005 * std::min (info.spacing (0), std::min (info.spacing (1), info.spacing (2)))); if (tck.size() < 2) return; - Math::Hermite hermite (0.1); + Math::Hermite hermite (0.1); - const PointF tck_proj_front = (tck[ 0 ] * 2.0) - tck[ 1 ]; - const PointF tck_proj_back = (tck[tck.size()-1] * 2.0) - tck[tck.size()-2]; + const point_type tck_proj_front = (tck[ 0 ] * 2.0) - tck[ 1 ]; + const point_type tck_proj_back = (tck[tck.size()-1] * 2.0) - tck[tck.size()-2]; unsigned int p = 0; - PointF p_voxel_exit = tck.front(); - float mu = 0.0; + point_type p_voxel_exit = tck.front(); + default_type mu = 0.0; bool end_track = false; Eigen::Vector3i next_voxel (round (scanner2voxel * tck.front())); do { - const PointF p_voxel_entry (p_voxel_exit); - PointF p_prev (p_voxel_entry); - float length = 0.0; - const float index_voxel_entry = float(p) + mu; + const point_type p_voxel_entry (p_voxel_exit); + point_type p_prev (p_voxel_entry); + default_type length = 0.0; + const default_type index_voxel_entry = default_type(p) + mu; const Eigen::Vector3i this_voxel = next_voxel; while ((p != tck.size()) && ((next_voxel = round (scanner2voxel * tck[p])) == this_voxel)) { @@ -191,20 +192,20 @@ namespace MR { end_track = true; } else { - float mu_min = mu; - float mu_max = 1.0; + default_type mu_min = mu; + default_type mu_max = 1.0; - const PointF* p_one = (p == 1) ? &tck_proj_front : &tck[p - 2]; - const PointF* p_four = (p == tck.size() - 1) ? &tck_proj_back : &tck[p + 1]; + const point_type* p_one = (p == 1) ? &tck_proj_front : &tck[p - 2]; + const point_type* p_four = (p == tck.size() - 1) ? &tck_proj_back : &tck[p + 1]; - PointF p_min = p_prev; - PointF p_max = tck[p]; + point_type p_min = p_prev; + point_type p_max = tck[p]; while ((p_min - p_max).squaredNorm() > accuracy) { mu = 0.5 * (mu_min + mu_max); hermite.set (mu); - const PointF p_mu = hermite.value (*p_one, tck[p - 1], tck[p], *p_four); + const point_type p_mu = hermite.value (*p_one, tck[p - 1], tck[p], *p_four); const Eigen::Vector3i mu_voxel = round (scanner2voxel * p_mu); if (mu_voxel == this_voxel) { @@ -222,11 +223,11 @@ namespace MR { } length += (p_prev - p_voxel_exit).norm(); - PointF traversal_vector = (p_voxel_exit - p_voxel_entry).normalized(); + Eigen::Vector3 traversal_vector = (p_voxel_exit - p_voxel_entry).cast().normalized(); if (traversal_vector.allFinite() && check (this_voxel, info)) { - const float index_voxel_exit = float(p) + mu; + const default_type index_voxel_exit = default_type(p) + mu; const size_t mean_tck_index = std::round (0.5 * (index_voxel_entry + index_voxel_exit)); - const float factor = tck_index_to_factor (mean_tck_index); + const default_type factor = tck_index_to_factor (mean_tck_index); add_to_set (out, this_voxel, traversal_vector, length, factor); } @@ -242,33 +243,33 @@ namespace MR { for (size_t end = 0; end != 2; ++end) { const Eigen::Vector3i vox = round (scanner2voxel * (end ? tck.back() : tck.front())); if (check (vox, info)) { - const Eigen::Vector3f dir = (end ? (tck[tck.size()-1] - tck[tck.size()-2]) : (tck[0] - tck[1])).normalized(); - const float factor = (end ? factors.back() : factors.front()); - add_to_set (out, vox, dir, 1.0f, factor); + const Eigen::Vector3 dir = (end ? (tck[tck.size()-1] - tck[tck.size()-2]) : (tck[0] - tck[1])).cast().normalized(); + const default_type factor = (end ? factors.back() : factors.front()); + add_to_set (out, vox, dir, 1.0, factor); } } } - inline void TrackMapper::add_to_set (SetVoxel& out, const Eigen::Vector3i& v, const Eigen::Vector3f& d, const float l, const float f) const + inline void TrackMapper::add_to_set (SetVoxel& out, const Eigen::Vector3i& v, const Eigen::Vector3& d, const default_type l, const default_type f) const { out.insert (v, l, f); } - inline void TrackMapper::add_to_set (SetVoxelDEC& out, const Eigen::Vector3i& v, const Eigen::Vector3f& d, const float l, const float f) const + inline void TrackMapper::add_to_set (SetVoxelDEC& out, const Eigen::Vector3i& v, const Eigen::Vector3& d, const default_type l, const default_type f) const { out.insert (v, d, l, f); } - inline void TrackMapper::add_to_set (SetDixel& out, const Eigen::Vector3i& v, const Eigen::Vector3f& d, const float l, const float f) const + inline void TrackMapper::add_to_set (SetDixel& out, const Eigen::Vector3i& v, const Eigen::Vector3& d, const default_type l, const default_type f) const { assert (dixel_plugin); const size_t bin = (*dixel_plugin) (d); out.insert (v, bin, l, f); } - inline void TrackMapper::add_to_set (SetVoxelTOD& out, const Eigen::Vector3i& v, const Eigen::Vector3f& d, const float l, const float f) const + inline void TrackMapper::add_to_set (SetVoxelTOD& out, const Eigen::Vector3i& v, const Eigen::Vector3& d, const default_type l, const default_type f) const { assert (tod_plugin); - Eigen::VectorXf sh; + VoxelTOD::vector_type sh; (*tod_plugin) (sh, d); out.insert (v, sh, l, f); } @@ -277,13 +278,13 @@ namespace MR { - inline float TrackMapper::tck_index_to_factor (const size_t i) const + inline default_type TrackMapper::tck_index_to_factor (const size_t i) const { - const float ideal_index = float(i) / float(upsampler.get_ratio()); + const default_type ideal_index = default_type(i) / default_type(upsampler.get_ratio()); const size_t lower_index = std::max (size_t(std::floor (ideal_index)), size_t(0)); const size_t upper_index = std::min (size_t(std::ceil (ideal_index)), size_t(factors.size() - 1)); - const float mu = ideal_index - float(lower_index); - return ((mu * factors[upper_index]) + ((1.0f-mu) * factors[lower_index])); + const default_type mu = ideal_index - default_type(lower_index); + return ((mu * factors[upper_index]) + ((1.0-mu) * factors[lower_index])); } diff --git a/src/dwi/tractography/mapping/gaussian/voxel.h b/src/dwi/tractography/mapping/gaussian/voxel.h index 43c15fe7b1..5fcad66751 100644 --- a/src/dwi/tractography/mapping/gaussian/voxel.h +++ b/src/dwi/tractography/mapping/gaussian/voxel.h @@ -34,15 +34,15 @@ namespace MR { { public: VoxelAddon() : sum_factors (0.0) { } - VoxelAddon (const float v) : sum_factors (v) { } - float get_factor() const { return sum_factors; } + VoxelAddon (const default_type v) : sum_factors (v) { } + default_type get_factor() const { return sum_factors; } protected: - void operator+= (const float f) const { sum_factors += f; } - void operator= (const float f) { sum_factors = f; } + void operator+= (const default_type f) const { sum_factors += f; } + void operator= (const default_type f) { sum_factors = f; } void operator= (const VoxelAddon& that) { sum_factors = that.sum_factors; } - void normalize (const float l) const { sum_factors /= l; } + void normalize (const default_type l) const { sum_factors /= l; } private: - mutable float sum_factors; + mutable default_type sum_factors; }; @@ -54,16 +54,16 @@ namespace MR { public: Voxel (const int x, const int y, const int z) : Base (x, y, z) , VoxelAddon () { } Voxel (const Eigen::Vector3i& that) : Base (that), VoxelAddon() { } - Voxel (const Eigen::Vector3i& v, const float l) : Base (v, l), VoxelAddon () { } - Voxel (const Eigen::Vector3i& v, const float l, const float f) : Base (v, l), VoxelAddon (f) { } + Voxel (const Eigen::Vector3i& v, const default_type l) : Base (v, l), VoxelAddon () { } + Voxel (const Eigen::Vector3i& v, const default_type l, const default_type f) : Base (v, l), VoxelAddon (f) { } Voxel () : Base (), VoxelAddon () { } Voxel& operator= (const Voxel& V) { Base::operator= (V); VoxelAddon::operator= (V); return *this; } - void operator+= (const float l) const { Base::operator+= (l); }; + void operator+= (const default_type l) const { Base::operator+= (l); } bool operator== (const Voxel& V) const { return Base::operator== (V); } bool operator< (const Voxel& V) const { return Base::operator< (V); } - void add (const float l, const float f) const { Base::operator+= (l); VoxelAddon::operator+= (f); } + void add (const default_type l, const default_type f) const { Base::operator+= (l); VoxelAddon::operator+= (f); } void normalize() const { VoxelAddon::normalize (get_length()); Base::normalize(); } }; @@ -75,18 +75,18 @@ namespace MR { public: VoxelDEC () : Base (), VoxelAddon () { } VoxelDEC (const Eigen::Vector3i& V) : Base (V), VoxelAddon () { } - VoxelDEC (const Eigen::Vector3i& V, const Eigen::Vector3f& d) : Base (V, d), VoxelAddon () { } - VoxelDEC (const Eigen::Vector3i& V, const Eigen::Vector3f& d, const float l) : Base (V, d, l), VoxelAddon () { } - VoxelDEC (const Eigen::Vector3i& V, const Eigen::Vector3f& d, const float l, const float f) : Base (V, d, l), VoxelAddon (f) { } + VoxelDEC (const Eigen::Vector3i& V, const Eigen::Vector3& d) : Base (V, d), VoxelAddon () { } + VoxelDEC (const Eigen::Vector3i& V, const Eigen::Vector3& d, const default_type l) : Base (V, d, l), VoxelAddon () { } + VoxelDEC (const Eigen::Vector3i& V, const Eigen::Vector3& d, const default_type l, const default_type f) : Base (V, d, l), VoxelAddon (f) { } VoxelDEC& operator= (const VoxelDEC& V) { Base::operator= (V); VoxelAddon::operator= (V); return (*this); } - void operator+= (const float) const { assert (0); } - void operator+= (const Eigen::Vector3f&) const { assert (0); }; + void operator+= (const default_type) const { assert (0); } + void operator+= (const Eigen::Vector3&) const { assert (0); } bool operator== (const VoxelDEC& V) const { return Base::operator== (V); } bool operator< (const VoxelDEC& V) const { return Base::operator< (V); } - void add (const Eigen::Vector3f&, const float) const { assert (0); }; - void add (const Eigen::Vector3f& i, const float l, const float f) const { Base::add (i, l); VoxelAddon::operator+= (f); } + void add (const Eigen::Vector3&, const default_type) const { assert (0); } + void add (const Eigen::Vector3& i, const default_type l, const default_type f) const { Base::add (i, l); VoxelAddon::operator+= (f); } void normalize() const { VoxelAddon::normalize (get_length()); Base::normalize(); } }; @@ -96,18 +96,19 @@ namespace MR { { typedef Mapping::Dixel Base; public: + typedef DWI::Directions::index_type dir_index_type; Dixel () : Base (), VoxelAddon () { } Dixel (const Eigen::Vector3i& V) : Base (V), VoxelAddon () { } - Dixel (const Eigen::Vector3i& V, const size_t b) : Base (V, b), VoxelAddon () { } - Dixel (const Eigen::Vector3i& V, const size_t b, const float l) : Base (V, b, l), VoxelAddon () { } - Dixel (const Eigen::Vector3i& V, const size_t b, const float l, const float f) : Base (V, b, l), VoxelAddon (f) { } + Dixel (const Eigen::Vector3i& V, const dir_index_type b) : Base (V, b), VoxelAddon () { } + Dixel (const Eigen::Vector3i& V, const dir_index_type b, const default_type l) : Base (V, b, l), VoxelAddon () { } + Dixel (const Eigen::Vector3i& V, const dir_index_type b, const default_type l, const default_type f) : Base (V, b, l), VoxelAddon (f) { } Dixel& operator= (const Dixel& V) { Base::operator= (V); VoxelAddon::operator= (V); return *this; } bool operator== (const Dixel& V) const { return Base::operator== (V); } bool operator< (const Dixel& V) const { return Base::operator< (V); } - void operator+= (const float) const { assert (0); }; + void operator+= (const default_type) const { assert (0); } - void add (const float l, const float f) const { Base::operator+= (l); VoxelAddon::operator+= (f); } + void add (const default_type l, const default_type f) const { Base::operator+= (l); VoxelAddon::operator+= (f); } void normalize() const { VoxelAddon::normalize (get_length()); Base::normalize(); } }; @@ -117,19 +118,20 @@ namespace MR { { typedef Mapping::VoxelTOD Base; public: + typedef Eigen::Matrix vector_type; VoxelTOD () : Base (), VoxelAddon () { } VoxelTOD (const Eigen::Vector3i& V) : Base (V), VoxelAddon () { } - VoxelTOD (const Eigen::Vector3i& V, const Eigen::VectorXf& t) : Base (V, t), VoxelAddon () { } - VoxelTOD (const Eigen::Vector3i& V, const Eigen::VectorXf& t, const float l) : Base (V, t, l), VoxelAddon () { } - VoxelTOD (const Eigen::Vector3i& V, const Eigen::VectorXf& t, const float l, const float f) : Base (V, t, l), VoxelAddon (f) { } + VoxelTOD (const Eigen::Vector3i& V, const vector_type& t) : Base (V, t), VoxelAddon () { } + VoxelTOD (const Eigen::Vector3i& V, const vector_type& t, const default_type l) : Base (V, t, l), VoxelAddon () { } + VoxelTOD (const Eigen::Vector3i& V, const vector_type& t, const default_type l, const default_type f) : Base (V, t, l), VoxelAddon (f) { } VoxelTOD& operator= (const VoxelTOD& V) { Base::operator= (V); VoxelAddon::operator= (V); return (*this); } bool operator== (const VoxelTOD& V) const { return Base::operator== (V); } bool operator< (const VoxelTOD& V) const { return Base::operator< (V); } - void operator+= (const Eigen::VectorXf&) const { assert (0); }; + void operator+= (const vector_type&) const { assert (0); } - void add (const Eigen::VectorXf&, const float) const { assert (0); }; - void add (const Eigen::VectorXf& i, const float l, const float f) const { Base::add (i, l); VoxelAddon::operator+= (f); } + void add (const vector_type&, const default_type) const { assert (0); } + void add (const vector_type& i, const default_type l, const default_type f) const { Base::add (i, l); VoxelAddon::operator+= (f); } void normalize() const { VoxelAddon::normalize (get_length()); Base::normalize(); } }; @@ -158,7 +160,7 @@ namespace MR { { public: typedef Voxel VoxType; - inline void insert (const Eigen::Vector3i& v, const float l, const float f) + inline void insert (const Eigen::Vector3i& v, const default_type l, const default_type f) { const Voxel temp (v, l, f); iterator existing = std::set::find (temp); @@ -172,7 +174,7 @@ namespace MR { { public: typedef VoxelDEC VoxType; - inline void insert (const Eigen::Vector3i& v, const Eigen::Vector3f& d, const float l, const float f) + inline void insert (const Eigen::Vector3i& v, const Eigen::Vector3& d, const default_type l, const default_type f) { const VoxelDEC temp (v, d, l, f); iterator existing = std::set::find (temp); @@ -186,7 +188,8 @@ namespace MR { { public: typedef Dixel VoxType; - inline void insert (const Eigen::Vector3i& v, const size_t d, const float l, const float f) + typedef Dixel::dir_index_type dir_index_type; + inline void insert (const Eigen::Vector3i& v, const dir_index_type d, const default_type l, const default_type f) { const Dixel temp (v, d, l, f); iterator existing = std::set::find (temp); @@ -200,7 +203,8 @@ namespace MR { { public: typedef VoxelTOD VoxType; - inline void insert (const Eigen::Vector3i& v, const Eigen::VectorXf& t, const float l, const float f) + typedef VoxelTOD::vector_type vector_type; + inline void insert (const Eigen::Vector3i& v, const vector_type& t, const default_type l, const default_type f) { const VoxelTOD temp (v, t, l, f); iterator existing = std::set::find (temp); diff --git a/src/dwi/tractography/mapping/mapper.cpp b/src/dwi/tractography/mapping/mapper.cpp index cca0049ec4..19e548ac90 100644 --- a/src/dwi/tractography/mapping/mapper.cpp +++ b/src/dwi/tractography/mapping/mapper.cpp @@ -48,7 +48,7 @@ void TrackMapperTWI::set_factor (const Streamline<>& tck, SetVoxelExtras& out) c case TDI: out.factor = 1.0; break; case LENGTH: out.factor = tck.calc_length(); break; - case INVLENGTH: out.factor = 1.0f / tck.calc_length(); break; + case INVLENGTH: out.factor = 1.0 / tck.calc_length(); break; case SCALAR_MAP: case SCALAR_MAP_COUNT: @@ -84,7 +84,7 @@ void TrackMapperTWI::set_factor (const Streamline<>& tck, SetVoxelExtras& out) c ++count; } } - out.factor = (count ? (out.factor / float(count)) : 0.0); + out.factor = (count ? (out.factor / default_type(count)) : 0.0); break; case T_MAX: @@ -112,7 +112,7 @@ void TrackMapperTWI::set_factor (const Streamline<>& tck, SetVoxelExtras& out) c ++count; } } - out.factor = (count ? (out.factor / float(count)) : 0.0); + out.factor = (count ? (out.factor / default_type(count)) : 0.0); break; case GAUSSIAN: @@ -216,7 +216,7 @@ void TrackMapperTWI::load_factors (const Streamline<>& tck) const if (contrast != CURVATURE) throw Exception ("Unsupported contrast in function TrackMapperTWI::load_factors()"); - std::vector tangents; + std::vector tangents; tangents.reserve (tck.size()); // Would like to be able to manipulate the length over which the tangent calculation is affected @@ -229,17 +229,17 @@ void TrackMapperTWI::load_factors (const Streamline<>& tck) const // Need to know the distance along the spline between every point and every other point // Start by logging the length of each step - std::vector step_sizes; + std::vector step_sizes; step_sizes.reserve (tck.size()); for (size_t i = 0; i != tck.size(); ++i) { - Eigen::Vector3f this_tangent; + Eigen::Vector3 this_tangent; if (i == 0) - this_tangent = ((tck[1] - tck[0] ).normalized()); + this_tangent = ((tck[1] - tck[0] ).cast().normalized()); else if (i == tck.size() - 1) - this_tangent = ((tck[i] - tck[i-1]).normalized()); + this_tangent = ((tck[i] - tck[i-1]).cast().normalized()); else - this_tangent = ((tck[i+1] - tck[i-1]).normalized()); + this_tangent = ((tck[i+1] - tck[i-1]).cast().normalized()); if (this_tangent.allFinite()) tangents.push_back (this_tangent); else @@ -271,8 +271,7 @@ void TrackMapperTWI::load_factors (const Streamline<>& tck) const } // Produce a matrix of spline distances between points - Eigen::MatrixXf spline_distances (tck.size(), tck.size()); - spline_distances.setZero(); + Eigen::Matrix spline_distances = Eigen::Matrix::Zero (tck.size(), tck.size()); for (size_t i = 0; i != tck.size(); ++i) { for (size_t j = 0; j <= i; ++j) { for (size_t k = i+1; k != tck.size(); ++k) { @@ -285,19 +284,19 @@ void TrackMapperTWI::load_factors (const Streamline<>& tck) const // Smooth both the tangent vectors and the principal normal vectors according to a Gaussuan kernel // Remember: tangent vectors are unit length, but for principal normal vectors length must be preserved! - std::vector smoothed_tangents; + std::vector smoothed_tangents; smoothed_tangents.reserve (tangents.size()); - static const float gaussian_theta = CURVATURE_TRACK_SMOOTHING_FWHM / (2.0 * sqrt (2.0 * log (2.0))); - static const float gaussian_denominator = 2.0 * gaussian_theta * gaussian_theta; + static const default_type gaussian_theta = CURVATURE_TRACK_SMOOTHING_FWHM / (2.0 * sqrt (2.0 * log (2.0))); + static const default_type gaussian_denominator = 2.0 * gaussian_theta * gaussian_theta; for (size_t i = 0; i != tck.size(); ++i) { - Eigen::Vector3f this_tangent (0.0, 0.0, 0.0); + Eigen::Vector3 this_tangent (0.0, 0.0, 0.0); for (size_t j = 0; j != tck.size(); ++j) { - const float distance = spline_distances (i, j); - const float this_weight = exp (-distance * distance / gaussian_denominator); + const default_type distance = spline_distances (i, j); + const default_type this_weight = exp (-distance * distance / gaussian_denominator); this_tangent += tangents[j] * this_weight; } @@ -307,7 +306,7 @@ void TrackMapperTWI::load_factors (const Streamline<>& tck) const for (size_t i = 0; i != tck.size(); ++i) { - float tangent_dot_product, length; + default_type tangent_dot_product, length; if (i == 0) { tangent_dot_product = smoothed_tangents[ 1 ].dot (smoothed_tangents[ 0 ]); length = spline_distances (0, 1); diff --git a/src/dwi/tractography/mapping/mapper.h b/src/dwi/tractography/mapping/mapper.h index 93b440798c..9de89f758e 100644 --- a/src/dwi/tractography/mapping/mapper.h +++ b/src/dwi/tractography/mapping/mapper.h @@ -159,11 +159,11 @@ namespace MR { virtual void postprocess (const Streamline<>& tck, SetVoxelExtras& out) const { } // Used by voxelise() and voxelise_precise() to increment the relevant set - inline void add_to_set (SetVoxel& , const Eigen::Vector3i&, const Eigen::Vector3f&, const float) const; - inline void add_to_set (SetVoxelDEC&, const Eigen::Vector3i&, const Eigen::Vector3f&, const float) const; - inline void add_to_set (SetVoxelDir&, const Eigen::Vector3i&, const Eigen::Vector3f&, const float) const; - inline void add_to_set (SetDixel& , const Eigen::Vector3i&, const Eigen::Vector3f&, const float) const; - inline void add_to_set (SetVoxelTOD&, const Eigen::Vector3i&, const Eigen::Vector3f&, const float) const; + inline void add_to_set (SetVoxel& , const Eigen::Vector3i&, const Eigen::Vector3&, const default_type) const; + inline void add_to_set (SetVoxelDEC&, const Eigen::Vector3i&, const Eigen::Vector3&, const default_type) const; + inline void add_to_set (SetVoxelDir&, const Eigen::Vector3i&, const Eigen::Vector3&, const default_type) const; + inline void add_to_set (SetDixel& , const Eigen::Vector3i&, const Eigen::Vector3&, const default_type) const; + inline void add_to_set (SetVoxelTOD&, const Eigen::Vector3i&, const Eigen::Vector3&, const default_type) const; DWI::Tractography::Resampling::Upsampler upsampler; @@ -182,18 +182,18 @@ namespace MR { for (auto i = tck.cbegin(); i != last; ++i) { vox = round (scanner2voxel * (*i)); if (check (vox, info)) { - const auto dir = (*(i+1) - *prev).normalized(); + const Eigen::Vector3 dir = (*(i+1) - *prev).cast().normalized(); if (dir.allFinite()) - add_to_set (output, vox, dir, 1.0f); + add_to_set (output, vox, dir, 1.0); } prev = i; } vox = round (scanner2voxel * (*last)); if (check (vox, info)) { - const auto dir = (*last - *prev).normalized(); + const Eigen::Vector3 dir = (*last - *prev).cast().normalized(); if (dir.allFinite()) - add_to_set (output, vox, dir, 1.0f); + add_to_set (output, vox, dir, 1.0); } for (auto& i : output) @@ -209,29 +209,30 @@ namespace MR { template void TrackMapperBase::voxelise_precise (const Streamline<>& tck, Cont& out) const { - typedef Eigen::Vector3f PointF; + typedef Streamline<>::point_type point_type; + typedef Streamline<>::value_type value_type; - const float accuracy = Math::pow2 (0.005 * std::min (info.spacing (0), std::min (info.spacing (1), info.spacing (2)))); + const default_type accuracy = Math::pow2 (0.005 * std::min (info.spacing (0), std::min (info.spacing (1), info.spacing (2)))); if (tck.size() < 2) return; - Math::Hermite hermite (0.1); + Math::Hermite hermite (0.1); - const PointF tck_proj_front = (tck[ 0 ] * 2.0) - tck[ 1 ]; - const PointF tck_proj_back = (tck[tck.size()-1] * 2.0) - tck[tck.size()-2]; + const point_type tck_proj_front = (tck[ 0 ] * 2.0) - tck[ 1 ]; + const point_type tck_proj_back = (tck[tck.size()-1] * 2.0) - tck[tck.size()-2]; unsigned int p = 0; - PointF p_voxel_exit = tck.front(); - float mu = 0.0; + point_type p_voxel_exit = tck.front(); + default_type mu = 0.0; bool end_track = false; auto next_voxel = round (scanner2voxel * tck.front()); do { - const PointF p_voxel_entry (p_voxel_exit); - PointF p_prev (p_voxel_entry); - float length = 0.0; + const point_type p_voxel_entry (p_voxel_exit); + point_type p_prev (p_voxel_entry); + default_type length = 0.0; const auto this_voxel = next_voxel; while ((p != tck.size()) && ((next_voxel = round (scanner2voxel * tck[p])) == this_voxel)) { @@ -247,20 +248,20 @@ namespace MR { } else { - float mu_min = mu; - float mu_max = 1.0; + default_type mu_min = mu; + default_type mu_max = 1.0; - const PointF* p_one = (p == 1) ? &tck_proj_front : &tck[p - 2]; - const PointF* p_four = (p == tck.size() - 1) ? &tck_proj_back : &tck[p + 1]; + const point_type* p_one = (p == 1) ? &tck_proj_front : &tck[p - 2]; + const point_type* p_four = (p == tck.size() - 1) ? &tck_proj_back : &tck[p + 1]; - PointF p_min = p_prev; - PointF p_max = tck[p]; + point_type p_min = p_prev; + point_type p_max = tck[p]; while ((p_min - p_max).squaredNorm() > accuracy) { mu = 0.5 * (mu_min + mu_max); hermite.set (mu); - const PointF p_mu = hermite.value (*p_one, tck[p - 1], tck[p], *p_four); + const point_type p_mu = hermite.value (*p_one, tck[p - 1], tck[p], *p_four); const Eigen::Vector3i mu_voxel = round (scanner2voxel * p_mu); if (mu_voxel == this_voxel) { @@ -279,7 +280,7 @@ namespace MR { } length += (p_prev - p_voxel_exit).norm(); - PointF traversal_vector = (p_voxel_exit - p_voxel_entry).normalized(); + const Eigen::Vector3 traversal_vector = (p_voxel_exit - p_voxel_entry).cast().normalized(); if (std::isfinite (traversal_vector[0]) && check (this_voxel, info)) add_to_set (out, this_voxel, traversal_vector, length); @@ -295,10 +296,10 @@ namespace MR { for (size_t end = 0; end != 2; ++end) { const auto vox = round (scanner2voxel * (end ? tck.back() : tck.front())); if (check (vox, info)) { - Streamline<>::point_type dir { NAN, NAN, NAN }; + Eigen::Vector3 dir { NaN, NaN, NaN }; if (tck.size() > 1) - dir = (end ? (tck[tck.size()-1] - tck[tck.size()-2]) : (tck[0] - tck[1])).normalized(); - add_to_set (out, vox, dir, 1.0f); + dir = (end ? (tck[tck.size()-1] - tck[tck.size()-2]) : (tck[0] - tck[1])).cast().normalized(); + add_to_set (out, vox, dir, 1.0); } } } @@ -307,28 +308,28 @@ namespace MR { // These are inlined to make as fast as possible - inline void TrackMapperBase::add_to_set (SetVoxel& out, const Eigen::Vector3i& v, const Eigen::Vector3f& d, const float l) const + inline void TrackMapperBase::add_to_set (SetVoxel& out, const Eigen::Vector3i& v, const Eigen::Vector3& d, const default_type l) const { out.insert (v, l); } - inline void TrackMapperBase::add_to_set (SetVoxelDEC& out, const Eigen::Vector3i& v, const Eigen::Vector3f& d, const float l) const + inline void TrackMapperBase::add_to_set (SetVoxelDEC& out, const Eigen::Vector3i& v, const Eigen::Vector3& d, const default_type l) const { out.insert (v, d, l); } - inline void TrackMapperBase::add_to_set (SetVoxelDir& out, const Eigen::Vector3i& v, const Eigen::Vector3f& d, const float l) const + inline void TrackMapperBase::add_to_set (SetVoxelDir& out, const Eigen::Vector3i& v, const Eigen::Vector3& d, const default_type l) const { out.insert (v, d, l); } - inline void TrackMapperBase::add_to_set (SetDixel& out, const Eigen::Vector3i& v, const Eigen::Vector3f& d, const float l) const + inline void TrackMapperBase::add_to_set (SetDixel& out, const Eigen::Vector3i& v, const Eigen::Vector3& d, const default_type l) const { assert (dixel_plugin); - const size_t bin = (*dixel_plugin) (d); + const DWI::Directions::index_type bin = (*dixel_plugin) (d); out.insert (v, bin, l); } - inline void TrackMapperBase::add_to_set (SetVoxelTOD& out, const Eigen::Vector3i& v, const Eigen::Vector3f& d, const float l) const + inline void TrackMapperBase::add_to_set (SetVoxelTOD& out, const Eigen::Vector3i& v, const Eigen::Vector3& d, const default_type l) const { assert (tod_plugin); - Eigen::VectorXf sh; + Eigen::Matrix sh; (*tod_plugin) (sh, d); out.insert (v, sh, l); } @@ -379,7 +380,7 @@ namespace MR { const tck_stat_t track_statistic; // Members for when the contribution of a track is not constant along its length - mutable std::vector factors; + mutable std::vector factors; void load_factors (const Streamline<>&) const; // Member for incorporating additional information from an external image into the TWI process diff --git a/src/dwi/tractography/mapping/mapper_plugins.cpp b/src/dwi/tractography/mapping/mapper_plugins.cpp index a6f94469ec..6e7b5a95f7 100644 --- a/src/dwi/tractography/mapping/mapper_plugins.cpp +++ b/src/dwi/tractography/mapping/mapper_plugins.cpp @@ -27,7 +27,7 @@ namespace MR { - const Eigen::Vector3f TWIImagePluginBase::get_last_point_in_fov (const std::vector& tck, const bool end) const + const Streamline<>::point_type TWIImagePluginBase::get_last_point_in_fov (const Streamline<>& tck, const bool end) const { int index = end ? tck.size() - 1 : 0; const int step = end ? -1 : 1; @@ -45,7 +45,7 @@ namespace MR { - void TWIScalarImagePlugin::load_factors (const std::vector& tck, std::vector& factors) + void TWIScalarImagePlugin::load_factors (const Streamline<>& tck, std::vector& factors) { if (statistic == ENDS_MIN || statistic == ENDS_MEAN || statistic == ENDS_MAX || statistic == ENDS_PROD) { @@ -75,7 +75,7 @@ namespace MR { - void TWIFODImagePlugin::load_factors (const std::vector& tck, std::vector& factors) + void TWIFODImagePlugin::load_factors (const Streamline<>& tck, std::vector& factors) { for (size_t i = 0; i != tck.size(); ++i) { if (interp.scanner (tck[i])) { @@ -83,7 +83,7 @@ namespace MR { for (auto l = Loop (3) (interp); l; ++l) sh_coeffs[interp.index(3)] = interp.value(); // Get the FOD amplitude along the streamline tangent - const Eigen::Vector3f dir = (tck[(i == tck.size()-1) ? i : (i+1)] - tck[i ? (i-1) : 0]).normalized(); + const Eigen::Vector3 dir = (tck[(i == tck.size()-1) ? i : (i+1)] - tck[i ? (i-1) : 0]).cast().normalized(); factors.push_back (precomputer->value (sh_coeffs, dir)); } else { factors.push_back (NaN); diff --git a/src/dwi/tractography/mapping/mapper_plugins.h b/src/dwi/tractography/mapping/mapper_plugins.h index c2ba432fd6..d213786828 100644 --- a/src/dwi/tractography/mapping/mapper_plugins.h +++ b/src/dwi/tractography/mapping/mapper_plugins.h @@ -25,6 +25,7 @@ #include "dwi/directions/set.h" +#include "dwi/tractography/streamline.h" #include "dwi/tractography/mapping/twi_stats.h" @@ -44,7 +45,7 @@ namespace MR { dirs (directions) { } DixelMappingPlugin (const DixelMappingPlugin& that) : dirs (that.dirs) { } - size_t operator() (const Eigen::Vector3f& d) const { return dirs.select_direction (d); } + DWI::Directions::index_type operator() (const Eigen::Vector3& d) const { return dirs.select_direction (d); } private: const DWI::Directions::FastLookupSet& dirs; }; @@ -75,7 +76,7 @@ namespace MR { virtual ~TWIImagePluginBase() { } - virtual void load_factors (const std::vector&, std::vector&) = 0; + virtual void load_factors (const Streamline<>&, std::vector&) = 0; protected: //Image voxel; @@ -84,7 +85,7 @@ namespace MR { mutable Interp::Linear> interp; // New helper function; find the last point on the streamline from which valid image information can be read - const Eigen::Vector3f get_last_point_in_fov (const std::vector&, const bool) const; + const Streamline<>::point_type get_last_point_in_fov (const Streamline<>&, const bool) const; }; @@ -114,7 +115,7 @@ namespace MR { ~TWIScalarImagePlugin() { } - void load_factors (const std::vector&, std::vector&); + void load_factors (const Streamline<>&, std::vector&); private: const tck_stat_t statistic; @@ -131,16 +132,16 @@ namespace MR { TWIFODImagePlugin (const std::string& input_image) : TWIImagePluginBase (input_image), sh_coeffs (interp.size(3)), - precomputer (new Math::SH::PrecomputedAL ()) { + precomputer (new Math::SH::PrecomputedAL ()) { Math::SH::check (Header (interp)); precomputer->init (Math::SH::LforN (sh_coeffs.size())); } - void load_factors (const std::vector&, std::vector&); + void load_factors (const Streamline<>&, std::vector&); private: - Eigen::VectorXf sh_coeffs; - std::shared_ptr> precomputer; + Eigen::Matrix sh_coeffs; + std::shared_ptr> precomputer; }; diff --git a/src/dwi/tractography/mapping/voxel.cpp b/src/dwi/tractography/mapping/voxel.cpp index beb206e3d8..3e1d4e13a8 100644 --- a/src/dwi/tractography/mapping/voxel.cpp +++ b/src/dwi/tractography/mapping/voxel.cpp @@ -23,7 +23,7 @@ namespace MR { namespace Mapping { - const size_t Dixel::invalid = std::numeric_limits::max(); + const Dixel::dir_index_type Dixel::invalid = std::numeric_limits::max(); } diff --git a/src/dwi/tractography/mapping/voxel.h b/src/dwi/tractography/mapping/voxel.h index 44c391a090..7724fb5766 100644 --- a/src/dwi/tractography/mapping/voxel.h +++ b/src/dwi/tractography/mapping/voxel.h @@ -22,6 +22,8 @@ #include "image.h" +#include "dwi/directions/set.h" + namespace MR { namespace DWI { @@ -31,10 +33,11 @@ namespace MR { // Helper functions; note that int[3] rather than Voxel is always used during the mapping itself - inline Eigen::Vector3i round (const Eigen::Vector3f& p) + template + inline Eigen::Vector3i round (const Eigen::Matrix& p) { assert (p.allFinite()); - return { int(std::lround (p[0])), int(std::lround (p[1])), int(std::lround (p[2])) }; + return { int(std::round (p[0])), int(std::round (p[1])), int(std::round (p[2])) }; } template @@ -43,7 +46,7 @@ namespace MR { return (V[0] >= 0 && V[0] < H.size(0) && V[1] >= 0 && V[1] < H.size(1) && V[2] >= 0 && V[2] < H.size(2)); } - inline Eigen::Vector3f vec2DEC (const Eigen::Vector3f& d) + inline Eigen::Vector3 vec2DEC (const Eigen::Vector3& d) { return { std::abs(d[0]), std::abs(d[1]), std::abs(d[2]) }; } @@ -56,15 +59,15 @@ namespace MR { public: Voxel (const int x, const int y, const int z) : Eigen::Vector3i (x,y,z), length (1.0f) { } Voxel (const Eigen::Vector3i& that) : Eigen::Vector3i (that), length (1.0f) { } - Voxel (const Eigen::Vector3i& v, const float l) : Eigen::Vector3i (v), length (l) { } - Voxel () : length (0.0f) { setZero(); } + Voxel (const Eigen::Vector3i& v, const default_type l) : Eigen::Vector3i (v), length (l) { } + Voxel () : length (0.0) { setZero(); } bool operator< (const Voxel& V) const { return (((*this)[2] == V[2]) ? (((*this)[1] == V[1]) ? ((*this)[0] < V[0]) : ((*this)[1] < V[1])) : ((*this)[2] < V[2])); } Voxel& operator= (const Voxel& V) { Eigen::Vector3i::operator= (V); length = V.length; return *this; } - void operator+= (const float l) const { length += l; } - void normalize() const { length = 1.0f; } - float get_length() const { return length; } + void operator+= (const default_type l) const { length += l; } + void normalize() const { length = 1.0; } + default_type get_length() const { return length; } private: - mutable float length; + mutable default_type length; }; @@ -74,35 +77,35 @@ namespace MR { public: VoxelDEC () : Voxel (), - colour (0.0f, 0.0f, 0.0f) { } + colour (0.0, 0.0, 0.0) { } VoxelDEC (const Eigen::Vector3i& V) : Voxel (V), - colour (0.0f, 0.0f, 0.0f) { } + colour (0.0, 0.0, 0.0) { } - VoxelDEC (const Eigen::Vector3i& V, const Eigen::Vector3f& d) : + VoxelDEC (const Eigen::Vector3i& V, const Eigen::Vector3& d) : Voxel (V), colour (vec2DEC (d)) { } - VoxelDEC (const Eigen::Vector3i& V, const Eigen::Vector3f& d, const float l) : + VoxelDEC (const Eigen::Vector3i& V, const Eigen::Vector3& d, const float l) : Voxel (V, l), colour (vec2DEC (d)) { } VoxelDEC& operator= (const VoxelDEC& V) { Voxel::operator= (V); colour = V.colour; return *this; } - VoxelDEC& operator= (const Eigen::Vector3i& V) { Voxel::operator= (V); colour = { 0.0f, 0.0f, 0.0f }; return *this; } + VoxelDEC& operator= (const Eigen::Vector3i& V) { Voxel::operator= (V); colour = { 0.0, 0.0, 0.0 }; return *this; } // For sorting / inserting, want to identify the same voxel, even if the colour is different bool operator== (const VoxelDEC& V) const { return Voxel::operator== (V); } bool operator< (const VoxelDEC& V) const { return Voxel::operator< (V); } void normalize() const { colour.normalize(); Voxel::normalize(); } - void set_dir (const Eigen::Vector3f& i) { colour = vec2DEC (i); } - void add (const Eigen::Vector3f& i, const float l) const { Voxel::operator+= (l); colour += vec2DEC (i); } - void operator+= (const Eigen::Vector3f& i) const { Voxel::operator+= (1.0f); colour += vec2DEC (i); } - const Eigen::Vector3f& get_colour() const { return colour; } + void set_dir (const Eigen::Vector3& i) { colour = vec2DEC (i); } + void add (const Eigen::Vector3& i, const default_type l) const { Voxel::operator+= (l); colour += vec2DEC (i); } + void operator+= (const Eigen::Vector3& i) const { Voxel::operator+= (1.0); colour += vec2DEC (i); } + const Eigen::Vector3& get_colour() const { return colour; } private: - mutable Eigen::Vector3f colour; + mutable Eigen::Vector3 colour; }; @@ -115,34 +118,34 @@ namespace MR { public: VoxelDir () : Voxel (), - dir (0.0f, 0.0f, 0.0f) { } + dir (0.0, 0.0, 0.0) { } VoxelDir (const Eigen::Vector3i& V) : Voxel (V), - dir (0.0f, 0.0f, 0.0f) { } + dir (0.0, 0.0, 0.0) { } - VoxelDir (const Eigen::Vector3i& V, const Eigen::Vector3f& d) : + VoxelDir (const Eigen::Vector3i& V, const Eigen::Vector3& d) : Voxel (V), dir (d) { } - VoxelDir (const Eigen::Vector3i& V, const Eigen::Vector3f& d, const float l) : + VoxelDir (const Eigen::Vector3i& V, const Eigen::Vector3& d, const default_type l) : Voxel (V, l), dir (d) { } VoxelDir& operator= (const VoxelDir& V) { Voxel::operator= (V); dir = V.dir; return *this; } - VoxelDir& operator= (const Eigen::Vector3i& V) { Voxel::operator= (V); dir = { 0.0f, 0.0f, 0.0f }; return *this; } + VoxelDir& operator= (const Eigen::Vector3i& V) { Voxel::operator= (V); dir = { 0.0, 0.0, 0.0 }; return *this; } bool operator== (const VoxelDir& V) const { return Voxel::operator== (V); } bool operator< (const VoxelDir& V) const { return Voxel::operator< (V); } void normalize() const { dir.normalize(); Voxel::normalize(); } - void set_dir (const Eigen::Vector3f& i) { dir = i; } - void add (const Eigen::Vector3f& i, const float l) const { Voxel::operator+= (l); dir += i * (dir.dot(i) < 0.0f ? -1.0f : 1.0f); } - void operator+= (const Eigen::Vector3f& i) const { Voxel::operator+= (1.0f); dir += i * (dir.dot(i) < 0.0f ? -1.0f : 1.0f); } - const Eigen::Vector3f& get_dir() const { return dir; } + void set_dir (const Eigen::Vector3& i) { dir = i; } + void add (const Eigen::Vector3& i, const default_type l) const { Voxel::operator+= (l); dir += i * (dir.dot(i) < 0.0 ? -1.0 : 1.0); } + void operator+= (const Eigen::Vector3& i) const { Voxel::operator+= (1.0); dir += i * (dir.dot(i) < 0.0 ? -1.0 : 1.0); } + const Eigen::Vector3& get_dir() const { return dir; } private: - mutable Eigen::Vector3f dir; + mutable Eigen::Vector3 dir; }; @@ -154,6 +157,9 @@ namespace MR { { public: + + typedef DWI::Directions::index_type dir_index_type; + Dixel () : Voxel (), dir (invalid) { } @@ -162,18 +168,18 @@ namespace MR { Voxel (V), dir (invalid) { } - Dixel (const Eigen::Vector3i& V, const size_t b) : + Dixel (const Eigen::Vector3i& V, const dir_index_type b) : Voxel (V), dir (b) { } - Dixel (const Eigen::Vector3i& V, const size_t b, const float l) : + Dixel (const Eigen::Vector3i& V, const dir_index_type b, const default_type l) : Voxel (V, l), dir (b) { } - void set_dir (const size_t b) { dir = b; } + void set_dir (const size_t b) { dir = b; } - bool valid() const { return (dir != invalid); } - size_t get_dir() const { return dir; } + bool valid() const { return (dir != invalid); } + dir_index_type get_dir() const { return dir; } Dixel& operator= (const Dixel& V) { Voxel::operator= (V); dir = V.dir; return *this; } Dixel& operator= (const Eigen::Vector3i& V) { Voxel::operator= (V); dir = invalid; return *this; } @@ -182,9 +188,9 @@ namespace MR { void operator+= (const float l) const { Voxel::operator+= (l); } private: - size_t dir; + dir_index_type dir; - static const size_t invalid; + static const dir_index_type invalid; }; @@ -196,6 +202,9 @@ namespace MR { { public: + + typedef Eigen::Matrix vector_type; + VoxelTOD () : Voxel (), sh_coefs () { } @@ -204,16 +213,16 @@ namespace MR { Voxel (V), sh_coefs () { } - VoxelTOD (const Eigen::Vector3i& V, const Eigen::VectorXf& t) : + VoxelTOD (const Eigen::Vector3i& V, const vector_type& t) : Voxel (V), sh_coefs (t) { } - VoxelTOD (const Eigen::Vector3i& V, const Eigen::VectorXf& t, const float l) : + VoxelTOD (const Eigen::Vector3i& V, const vector_type& t, const default_type l) : Voxel (V, l), sh_coefs (t) { } - VoxelTOD& operator= (const VoxelTOD& V) { Voxel::operator= (V); sh_coefs = V.sh_coefs; return (*this); } - VoxelTOD& operator= (const Eigen::Vector3i& V) { Voxel::operator= (V); sh_coefs.resize(0,0); return (*this); } + VoxelTOD& operator= (const VoxelTOD& V) { Voxel::operator= (V); sh_coefs = V.sh_coefs; return (*this); } + VoxelTOD& operator= (const Eigen::Vector3i& V) { Voxel::operator= (V); sh_coefs.resize(0); return (*this); } // For sorting / inserting, want to identify the same voxel, even if the TOD is different bool operator== (const VoxelTOD& V) const { return Voxel::operator== (V); } @@ -221,28 +230,28 @@ namespace MR { void normalize() const { - const float multiplier = 1.0f / get_length(); + const default_type multiplier = 1.0 / get_length(); sh_coefs *= multiplier; Voxel::normalize(); } - void set_tod (const Eigen::VectorXf& i) { sh_coefs = i; } - void add (const Eigen::VectorXf& i, const float l) const + void set_tod (const vector_type& i) { sh_coefs = i; } + void add (const vector_type& i, const default_type l) const { assert (i.size() == sh_coefs.size()); for (ssize_t index = 0; index != sh_coefs.size(); ++index) sh_coefs[index] += l * i[index]; Voxel::operator+= (l); } - void operator+= (const Eigen::VectorXf& i) const + void operator+= (const vector_type& i) const { assert (i.size() == sh_coefs.size()); sh_coefs += i; - Voxel::operator+= (1.0f); + Voxel::operator+= (1.0); } - const Eigen::VectorXf& get_tod() const { return sh_coefs; } + const vector_type& get_tod() const { return sh_coefs; } private: - mutable Eigen::VectorXf sh_coefs; + mutable vector_type sh_coefs; }; @@ -255,9 +264,9 @@ namespace MR { class SetVoxelExtras { public: - float factor; // For TWI, when contribution to the map is uniform along the length of the track + default_type factor; // For TWI, when contribution to the map is uniform along the length of the track size_t index; // Index of the track - float weight; // Cross-sectional multiplier for the track + default_type weight; // Cross-sectional multiplier for the track }; @@ -279,7 +288,7 @@ namespace MR { else (*existing) += v.get_length(); } - inline void insert (const Eigen::Vector3i& v, const float l) + inline void insert (const Eigen::Vector3i& v, const default_type l) { const Voxel temp (v, l); insert (temp); @@ -302,12 +311,12 @@ namespace MR { else existing->add (v.get_colour(), v.get_length()); } - inline void insert (const Eigen::Vector3i& v, const Eigen::Vector3f& d) + inline void insert (const Eigen::Vector3i& v, const Eigen::Vector3& d) { const VoxelDEC temp (v, d); insert (temp); } - inline void insert (const Eigen::Vector3i& v, const Eigen::Vector3f& d, const float l) + inline void insert (const Eigen::Vector3i& v, const Eigen::Vector3& d, const default_type l) { const VoxelDEC temp (v, d, l); insert (temp); @@ -329,12 +338,12 @@ namespace MR { else existing->add (v.get_dir(), v.get_length()); } - inline void insert (const Eigen::Vector3i& v, const Eigen::Vector3f& d) + inline void insert (const Eigen::Vector3i& v, const Eigen::Vector3& d) { const VoxelDir temp (v, d); insert (temp); } - inline void insert (const Eigen::Vector3i& v, const Eigen::Vector3f& d, const float l) + inline void insert (const Eigen::Vector3i& v, const Eigen::Vector3& d, const default_type l) { const VoxelDir temp (v, d, l); insert (temp); @@ -346,6 +355,7 @@ namespace MR { { public: typedef Dixel VoxType; + typedef Dixel::dir_index_type dir_index_type; inline void insert (const Dixel& v) { iterator existing = std::set::find (v); @@ -354,12 +364,12 @@ namespace MR { else (*existing) += v.get_length(); } - inline void insert (const Eigen::Vector3i& v, const size_t d) + inline void insert (const Eigen::Vector3i& v, const dir_index_type d) { const Dixel temp (v, d); insert (temp); } - inline void insert (const Eigen::Vector3i& v, const size_t d, const float l) + inline void insert (const Eigen::Vector3i& v, const dir_index_type d, const default_type l) { const Dixel temp (v, d, l); insert (temp); @@ -374,6 +384,7 @@ namespace MR { { public: typedef VoxelTOD VoxType; + typedef VoxelTOD::vector_type vector_type; inline void insert (const VoxelTOD& v) { iterator existing = std::set::find (v); @@ -382,12 +393,12 @@ namespace MR { else (*existing) += v.get_tod(); } - inline void insert (const Eigen::Vector3i& v, const Eigen::VectorXf& t) + inline void insert (const Eigen::Vector3i& v, const vector_type& t) { const VoxelTOD temp (v, t); insert (temp); } - inline void insert (const Eigen::Vector3i& v, const Eigen::VectorXf& t, const float l) + inline void insert (const Eigen::Vector3i& v, const vector_type& t, const default_type l) { const VoxelTOD temp (v, t, l); insert (temp); diff --git a/src/dwi/tractography/mapping/writer.h b/src/dwi/tractography/mapping/writer.h index 6f1517e77a..51005142c6 100644 --- a/src/dwi/tractography/mapping/writer.h +++ b/src/dwi/tractography/mapping/writer.h @@ -159,7 +159,7 @@ namespace MR { for (auto l = loop (buffer, *counts); l; ++l) { if (counts->value()) { auto value = get_dec(); - const float norm = value.norm(); + const default_type norm = value.norm(); if (norm) value *= counts->value() / norm; set_dec (value); @@ -192,7 +192,7 @@ namespace MR { else if (type == TOD) { for (auto l = loop (buffer, *counts); l; ++l) { if (counts->value()) { - Eigen::VectorXf value; + VoxelTOD::vector_type value; get_tod (value); value *= (1.0 / counts->value()); set_tod (value); @@ -203,7 +203,7 @@ namespace MR { // rather than a per-dixel mean? for (auto l = Loop (buffer) (buffer, *counts); l; ++l) { if (counts->value()) - buffer.value() /= float(counts->value()); + buffer.value() /= default_type(counts->value()); } } break; @@ -256,23 +256,23 @@ namespace MR { // For the standard SetVoxel classes, this is a single value 'factor' for the set as // stored in SetVoxelExtras // For the Gaussian SetVoxel classes, there is a factor per mapped element - float get_factor (const Voxel& element, const SetVoxel& set) const { return set.factor; } - float get_factor (const VoxelDEC& element, const SetVoxelDEC& set) const { return set.factor; } - float get_factor (const Dixel& element, const SetDixel& set) const { return set.factor; } - float get_factor (const VoxelTOD& element, const SetVoxelTOD& set) const { return set.factor; } - float get_factor (const Gaussian::Voxel& element, const Gaussian::SetVoxel& set) const { return element.get_factor(); } - float get_factor (const Gaussian::VoxelDEC& element, const Gaussian::SetVoxelDEC& set) const { return element.get_factor(); } - float get_factor (const Gaussian::Dixel& element, const Gaussian::SetDixel& set) const { return element.get_factor(); } - float get_factor (const Gaussian::VoxelTOD& element, const Gaussian::SetVoxelTOD& set) const { return element.get_factor(); } + default_type get_factor (const Voxel& element, const SetVoxel& set) const { return set.factor; } + default_type get_factor (const VoxelDEC& element, const SetVoxelDEC& set) const { return set.factor; } + default_type get_factor (const Dixel& element, const SetDixel& set) const { return set.factor; } + default_type get_factor (const VoxelTOD& element, const SetVoxelTOD& set) const { return set.factor; } + default_type get_factor (const Gaussian::Voxel& element, const Gaussian::SetVoxel& set) const { return element.get_factor(); } + default_type get_factor (const Gaussian::VoxelDEC& element, const Gaussian::SetVoxelDEC& set) const { return element.get_factor(); } + default_type get_factor (const Gaussian::Dixel& element, const Gaussian::SetDixel& set) const { return element.get_factor(); } + default_type get_factor (const Gaussian::VoxelTOD& element, const Gaussian::SetVoxelTOD& set) const { return element.get_factor(); } // Convenience functions for Directionally-Encoded Colour processing - Eigen::Vector3f get_dec (); - void set_dec (const Eigen::Vector3f&); + Eigen::Vector3 get_dec (); + void set_dec (const Eigen::Vector3&); // Convenience functions for Track Orientation Distribution processing - void get_tod ( Eigen::VectorXf&); - void set_tod (const Eigen::VectorXf&); + void get_tod ( VoxelTOD::vector_type&); + void set_tod (const VoxelTOD::vector_type&); }; @@ -289,12 +289,12 @@ namespace MR { assert (MapWriterBase::type == GREYSCALE); for (const auto& i : in) { assign_pos_of (i).to (buffer); - const float factor = get_factor (i, in); - const float weight = in.weight * i.get_length(); + const default_type factor = get_factor (i, in); + const default_type weight = in.weight * i.get_length(); switch (voxel_statistic) { case V_SUM: buffer.value() += weight * 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_MIN: buffer.value() = std::min (default_type (buffer.value()), factor); break; + case V_MAX: buffer.value() = std::max (default_type (buffer.value()), factor); break; case V_MEAN: buffer.value() += weight * factor; assert (counts); @@ -316,8 +316,8 @@ namespace MR { assert (type == DEC); for (const auto& i : in) { assign_pos_of (i).to (buffer); - const float factor = get_factor (i, in); - const float weight = in.weight * i.get_length(); + const default_type factor = get_factor (i, in); + const default_type weight = in.weight * i.get_length(); auto scaled_colour = i.get_colour(); scaled_colour *= factor; const auto current_value = get_dec(); @@ -357,12 +357,12 @@ namespace MR { for (const auto& i : in) { assign_pos_of (i, 0, 3).to (buffer); buffer.index(3) = i.get_dir(); - const float factor = get_factor (i, in); - const float weight = in.weight * i.get_length(); + const default_type factor = get_factor (i, in); + const default_type weight = in.weight * i.get_length(); switch (voxel_statistic) { case V_SUM: buffer.value() += weight * 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_MIN: buffer.value() = std::min (default_type (buffer.value()), factor); break; + case V_MAX: buffer.value() = std::max (default_type (buffer.value()), factor); break; case V_MEAN: buffer.value() += weight * factor; assert (counts); @@ -383,11 +383,11 @@ namespace MR { void MapWriter::receive_tod (const Cont& in) { assert (type == TOD); - Eigen::VectorXf sh_coefs; + VoxelTOD::vector_type sh_coefs; for (const auto& i : in) { assign_pos_of (i, 0, 3).to (buffer); - const float factor = get_factor (i, in); - const float weight = in.weight * i.get_length(); + const default_type factor = get_factor (i, in); + const default_type weight = in.weight * i.get_length(); get_tod (sh_coefs); if (counts) assign_pos_of (i, 0, 3).to (*counts); @@ -434,10 +434,10 @@ namespace MR { template - Eigen::Vector3f MapWriter::get_dec () + Eigen::Vector3 MapWriter::get_dec () { assert (type == DEC); - Eigen::Vector3f value; + Eigen::Vector3 value; buffer.index(3) = 0; value[0] = buffer.value(); ++buffer.index(3); value[1] = buffer.value(); ++buffer.index(3); value[2] = buffer.value(); @@ -445,7 +445,7 @@ namespace MR { } template - void MapWriter::set_dec (const Eigen::Vector3f& value) + void MapWriter::set_dec (const Eigen::Vector3& value) { assert (type == DEC); buffer.index(3) = 0; buffer.value() = value[0]; @@ -458,7 +458,7 @@ namespace MR { template - void MapWriter::get_tod (Eigen::VectorXf& sh_coefs) + void MapWriter::get_tod (VoxelTOD::vector_type& sh_coefs) { assert (type == TOD); sh_coefs.resize (buffer.size(3)); @@ -467,7 +467,7 @@ namespace MR { } template - void MapWriter::set_tod (const Eigen::VectorXf& sh_coefs) + void MapWriter::set_tod (const VoxelTOD::vector_type& sh_coefs) { assert (type == TOD); assert (sh_coefs.size() == buffer.size(3)); diff --git a/src/dwi/tractography/seeding/dynamic.cpp b/src/dwi/tractography/seeding/dynamic.cpp index f5ae1bc04e..0ffb7cb3b9 100644 --- a/src/dwi/tractography/seeding/dynamic.cpp +++ b/src/dwi/tractography/seeding/dynamic.cpp @@ -230,7 +230,7 @@ namespace MR } } if (good_seed) { - d = fixel.get_dir(); + d = fixel.get_dir().cast(); #ifdef DYNAMIC_SEED_DEBUGGING write_seed (p); #endif diff --git a/src/gui/dwi/renderer.cpp b/src/gui/dwi/renderer.cpp index 34b6de55a9..cdac7f9edf 100644 --- a/src/gui/dwi/renderer.cpp +++ b/src/gui/dwi/renderer.cpp @@ -637,9 +637,11 @@ namespace MR void Renderer::Dixel::update_dixels (const MR::DWI::Directions::Set& dirs) { + std::vector directions_data; std::vector> indices_data; for (size_t i = 0; i != dirs.size(); ++i) { + directions_data.push_back (dirs[i].cast()); for (auto j : dirs.get_adj_dirs(i)) { if (j > i) { for (auto k : dirs.get_adj_dirs(j)) { @@ -650,15 +652,15 @@ namespace MR if (I == i) { // Invert a direction if required - std::array d {{ dirs[i], dirs[j], dirs[k] }}; - const Eigen::Vector3f mean_dir ((d[0]+d[1]+d[2]).normalized()); + std::array d {{ dirs[i], dirs[j], dirs[k] }}; + const Eigen::Vector3 mean_dir ((d[0]+d[1]+d[2]).normalized()); for (size_t v = 0; v != 3; ++v) { - if (d[v].dot (mean_dir) < 0.0f) + if (d[v].dot (mean_dir) < 0.0) d[v] = -d[v]; } // Conform to right hand rule - const Eigen::Vector3f normal (((d[1]-d[0]).cross (d[2]-d[1])).normalized()); - if (normal.dot (mean_dir) < 0.0f) + const Eigen::Vector3 normal (((d[1]-d[0]).cross (d[2]-d[1])).normalized()); + if (normal.dot (mean_dir) < 0.0) indices_data.push_back ( {{GLint(i), GLint(k), GLint(j)}} ); else indices_data.push_back ( {{GLint(i), GLint(j), GLint(k)}} ); @@ -676,7 +678,7 @@ namespace MR Renderer::GrabContext context (parent.context_); VAO.bind(); vertex_buffer.bind (gl::ARRAY_BUFFER); - gl::BufferData (gl::ARRAY_BUFFER, dirs.size()*sizeof(Eigen::Vector3f), &dirs.get_dirs()[0][0], gl::STATIC_DRAW); + gl::BufferData (gl::ARRAY_BUFFER, dirs.size()*sizeof(Eigen::Vector3f), &directions_data[0], gl::STATIC_DRAW); gl::VertexAttribPointer (0, 3, gl::FLOAT, gl::FALSE_, 3*sizeof(GLfloat), (void*)0); index_buffer.bind(); gl::BufferData (gl::ELEMENT_ARRAY_BUFFER, indices_data.size()*sizeof(std::array), &indices_data[0], gl::STATIC_DRAW); diff --git a/src/gui/dwi/renderer.h b/src/gui/dwi/renderer.h index 47e6ae190b..1b0a86c958 100644 --- a/src/gui/dwi/renderer.h +++ b/src/gui/dwi/renderer.h @@ -200,7 +200,7 @@ namespace MR class Dixel : public ModeBase { - typedef MR::DWI::Directions::dir_t dir_t; + typedef MR::DWI::Directions::index_type dir_t; public: Dixel (Renderer& parent) : ModeBase (parent), diff --git a/src/stats/cfe.cpp b/src/stats/cfe.cpp index 38e638e83b..66a947dc49 100644 --- a/src/stats/cfe.cpp +++ b/src/stats/cfe.cpp @@ -50,7 +50,7 @@ namespace MR int32_t last_index = first_index + fixel_indexer.value(); int32_t closest_fixel_index = -1; value_type largest_dp = 0.0; - direction_type dir (i->get_dir().normalized()); + const direction_type dir (i->get_dir().normalized()); for (int32_t j = first_index; j < last_index; ++j) { const value_type dp = std::abs (dir.dot (fixel_directions[j])); if (dp > largest_dp) { diff --git a/src/stats/cfe.h b/src/stats/cfe.h index ed4858f111..85ed409ec5 100644 --- a/src/stats/cfe.h +++ b/src/stats/cfe.h @@ -32,7 +32,7 @@ namespace MR typedef Math::Stats::value_type value_type; typedef Math::Stats::vector_type vector_type; typedef float connectivity_value_type; - typedef Eigen::Matrix direction_type; + typedef Eigen::Matrix direction_type; typedef Eigen::Array connectivity_vector_type; typedef DWI::Tractography::Mapping::SetVoxelDir SetVoxelDir; From eddf766edb993e9f348b1f695f664a62c5217de7 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sat, 2 Jul 2016 03:47:16 +1000 Subject: [PATCH 017/723] Surface::FreeSurfer::read_annot() function Imports annotation files from FreeSurfer. Not currently used in any commands. --- src/surface/freesurfer.cpp | 119 +++++++++++++++++++++++++++++++++++++ src/surface/freesurfer.h | 9 +++ src/surface/types.h | 4 +- 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 src/surface/freesurfer.cpp diff --git a/src/surface/freesurfer.cpp b/src/surface/freesurfer.cpp new file mode 100644 index 0000000000..718a85ea54 --- /dev/null +++ b/src/surface/freesurfer.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + +#include "surface/freesurfer.h" + +#include "debug.h" + +namespace MR +{ + namespace Surface + { + namespace FreeSurfer + { + + + + void read_annot (const std::string& path, label_vector_type& labels, Connectome::LUT& lut) + { + std::ifstream in (path.c_str(), std::ios_base::in | std::ios_base::binary); + if (!in) + throw Exception ("Error opening input file!"); + + const int32_t num_vertices = get_BE (in); + std::vector vertices, vertex_labels; + vertices.reserve (num_vertices); + vertex_labels.reserve (num_vertices); + for (int32_t i = 0; i != num_vertices; ++i) { + vertices.push_back (get_BE (in)); + vertex_labels.push_back (get_BE (in)); + } + + const int32_t colortable_present = get_BE (in); + if (!in.good()) { + WARN ("FreeSurfer annotation file \"" + Path::basename (path) + "\" does not contain colortable information"); + labels = label_vector_type::Zero (num_vertices); + for (size_t i = 0; i != vertices.size(); ++i) + labels[vertices[i]] = vertex_labels[i]; + return; + } + if (!colortable_present) + throw Exception ("Error reading FreeSurfer annotation file \"" + Path::basename (path) + "\": Unexpected colortable flag"); + + // Structure that will map from the colour-based structure identifier to a more sensible index + std::map rgb2index; + + int32_t num_entries = get_BE (in); + if (num_entries > 0) { + + const int32_t orig_lut_name_length = get_BE (in); + std::unique_ptr orig_lut_name (new char[orig_lut_name_length]); + in.read (orig_lut_name.get(), orig_lut_name_length); + for (int32_t i = 0; i != num_entries; ++i) { + const int32_t struct_name_length = get_BE (in); + std::unique_ptr struct_name (new char[struct_name_length]); + in.read (struct_name.get(), struct_name_length); + const int32_t r = get_BE (in); + const int32_t g = get_BE (in); + const int32_t b = get_BE (in); + const int32_t flag = get_BE (in); + const int32_t id = r + (g << 8) + (b << 16) + (flag << 24); + lut.insert (std::make_pair (i, Connectome::LUT_node (std::string (struct_name.get()), r, g, b))); + rgb2index[id] = i; + } + + } else { + + const int32_t version = -num_entries; + if (version != 2) + throw Exception ("Error reading FreeSurfer annotation file \"" + Path::basename (path) + "\": Unsupported file version (" + str(version) + ")"); + + num_entries = get_BE (in); + const int32_t orig_lut_name_length = get_BE (in); + std::unique_ptr orig_lut_name (new char[orig_lut_name_length]); + in.read (orig_lut_name.get(), orig_lut_name_length); + + const int32_t num_entries_to_read = get_BE (in); + for (int32_t i = 0; i != num_entries_to_read; ++i) { + const int32_t structure = get_BE (in) + 1; + if (structure < 0) + throw Exception ("Error reading FreeSurfer annotation file \"" + Path::basename (path) + "\": Negative structure index"); + if (lut.find (structure) != lut.end()) + throw Exception ("Error reading FreeSurfer annotation file \"" + Path::basename (path) + "\": Duplicate structure index"); + const int32_t struct_name_length = get_BE (in); + std::unique_ptr struct_name (new char[struct_name_length]); + in.read (struct_name.get(), struct_name_length); + const int32_t r = get_BE (in); + const int32_t g = get_BE (in); + const int32_t b = get_BE (in); + const int32_t flag = get_BE (in); + const int32_t id = r + (g << 8) + (b << 16) + (flag << 24); + lut.insert (std::make_pair (structure, Connectome::LUT_node (std::string (struct_name.get()), r, g, b))); + rgb2index[id] = structure; + } + + } + + labels = label_vector_type::Zero (num_vertices); + for (size_t i = 0; i != vertices.size(); ++i) + labels[vertices[i]] = rgb2index[vertex_labels[i]]; + } + + + + } + } +} diff --git a/src/surface/freesurfer.h b/src/surface/freesurfer.h index 0612e431cb..d6183fd192 100644 --- a/src/surface/freesurfer.h +++ b/src/surface/freesurfer.h @@ -18,9 +18,14 @@ #define __surface_freesurfer_h__ #include +#include #include "raw.h" +#include "connectome/lut.h" + +#include "surface/types.h" + namespace MR { namespace Surface @@ -55,6 +60,10 @@ namespace MR + void read_annot (const std::string&, label_vector_type&, Connectome::LUT&); + + + } } } diff --git a/src/surface/types.h b/src/surface/types.h index 47d097089e..0177b24cd9 100644 --- a/src/surface/types.h +++ b/src/surface/types.h @@ -17,7 +17,7 @@ #define __surface_types_h__ - +#include "connectome/connectome.h" #include "surface/polygon.h" @@ -45,6 +45,8 @@ namespace MR } }; + typedef Eigen::Array label_vector_type; + } From 9668b179ea5c3fe728d0cf359e7bf5d655050746 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sat, 2 Jul 2016 07:03:30 +1000 Subject: [PATCH 018/723] Surface::Freesurfer::read_label() function This function reads in a FreeSurfer .label file, resulting in a vector of vertices (which will typically be a subset of all vertices in the original mesh), and a Surface::Scalar (one quantitative value associated with each vertex). Also changed Surface::Scalar to derive from Eigen::Array rather than having it as a member. --- src/surface/freesurfer.cpp | 45 ++++++++++++++++++++++++++++++++++++++ src/surface/freesurfer.h | 2 ++ src/surface/scalar.cpp | 33 +++++++++++++--------------- src/surface/scalar.h | 26 +++++++++------------- 4 files changed, 72 insertions(+), 34 deletions(-) diff --git a/src/surface/freesurfer.cpp b/src/surface/freesurfer.cpp index 718a85ea54..f1ad06a3f6 100644 --- a/src/surface/freesurfer.cpp +++ b/src/surface/freesurfer.cpp @@ -114,6 +114,51 @@ namespace MR + void read_label (const std::string& path, VertexList& vertices, Scalar& scalar) + { + vertices.clear(); + scalar.resize(0); + + std::ifstream in (path.c_str(), std::ios_base::in); + if (!in) + throw Exception ("Error opening input file!"); + + std::string line; + std::getline (in, line); + if (line.substr(0, 13) != "#!ascii label") + throw Exception ("Error parsing FreeSurfer label file \"" + Path::basename (path) + "\": Bad first line identifier"); + std::getline (in, line); + uint32_t num_vertices = 0; + try { + num_vertices = to (line); + } catch (Exception& e) { + throw Exception (e, "Error parsing FreeSurfer label file \"" + Path::basename (path) + "\": Bad second line vertex count"); + } + + for (size_t i = 0; i != num_vertices; ++i) { + std::getline (in, line); + uint32_t index = std::numeric_limits::max(); + default_type x = NaN, y = NaN, z = NaN, value = NaN; + sscanf (line.c_str(), "%u %lf %lf %lf %lf", &index, &x, &y, &z, &value); + if (index == std::numeric_limits::max()) + throw Exception ("Error parsing FreeSurfer label file \"" + Path::basename (path) + "\": Malformed line"); + if (index >= scalar.size()) { + scalar.conservativeResizeLike (Scalar::Base::Constant (index+1, NaN)); + vertices.resize (index+1, Vertex (NaN, NaN, NaN)); + } + if (std::isfinite (scalar[index])) + throw Exception ("Error parsing FreeSurfer label file \"" + Path::basename (path) + "\": Duplicate indices"); + scalar[index] = value; + vertices[index] = Vertex (x, y, z); + } + + if (!in.good()) + throw Exception ("Error parsing FreeSurfer label file \"" + Path::basename (path) + "\": End of file reached"); + scalar.set_name (path); + } + + + } } } diff --git a/src/surface/freesurfer.h b/src/surface/freesurfer.h index d6183fd192..1433d3ba75 100644 --- a/src/surface/freesurfer.h +++ b/src/surface/freesurfer.h @@ -24,6 +24,7 @@ #include "connectome/lut.h" +#include "surface/scalar.h" #include "surface/types.h" namespace MR @@ -61,6 +62,7 @@ namespace MR void read_annot (const std::string&, label_vector_type&, Connectome::LUT&); + void read_label (const std::string&, VertexList&, Scalar&); diff --git a/src/surface/scalar.cpp b/src/surface/scalar.cpp index 3497e09eb7..4823c994bd 100644 --- a/src/surface/scalar.cpp +++ b/src/surface/scalar.cpp @@ -31,7 +31,7 @@ namespace MR Scalar::Scalar (const std::string& path, const Mesh& mesh) { try { - values = load_vector (path); + load_vector (path); } catch (...) { try { load_fs_w (path, mesh); @@ -43,7 +43,7 @@ namespace MR } } } - if (size_t(values.size()) != mesh.num_vertices()) + if (size_t(size()) != mesh.num_vertices()) throw Exception ("Input surface scalar file \"" + path + "\" has incorrect number of vertices"); name = Path::basename (path); } @@ -52,7 +52,7 @@ namespace MR void Scalar::save (const std::string& path) const { - save_vector (values, path); + save_vector (*this, path); } @@ -65,15 +65,15 @@ namespace MR FreeSurfer::get_BE (in); // 'latency' const int32_t num_entries = FreeSurfer::get_int24_BE (in); - values = vector_type::Zero (num_vertices); - for (size_t i = 0; i != num_entries; ++i) { + Base::operator= (Base::Zero (num_entries)); + for (int32_t i = 0; i != num_entries; ++i) { const int32_t index = FreeSurfer::get_int24_BE (in); const float value = FreeSurfer::get_BE (in); - if (index >= mesh.num_vertices()) + if (size_t(index) >= mesh.num_vertices()) throw Exception ("Error opening file \"" + path + "\" as FreeSurfer w-file: invalid vertex index"); if (!in.good()) throw Exception ("Error opening file \"" + path + "\" as FreeSurfer w-file: truncated file"); - values[index] = value; + (*this)[index] = value; } } @@ -89,46 +89,43 @@ namespace MR if (magic_number == FreeSurfer::new_curv_file_magic_number) { const int32_t num_vertices = FreeSurfer::get_BE (in); - if (num_vertices != mesh.num_vertices()) + if (size_t(num_vertices) != mesh.num_vertices()) throw Exception ("Error opening file \"" + path + "\" as Freesurfer curv file: Incorrect number of vertices"); const int32_t num_faces = FreeSurfer::get_BE (in); - if (num_faces != mesh.num_polygons()) + if (size_t(num_faces) != mesh.num_polygons()) throw Exception ("Error opening file \"" + path + "\" as Freesurfer curv file: Incorrect number of polygons"); const int32_t vals_per_vertex = FreeSurfer::get_BE (in); if (vals_per_vertex != 1) throw Exception ("Error opening file \"" + path + "\" as Freesurfer curv file: Only support 1 value per vertex"); - values.resize (num_vertices); + (*this).resize (num_vertices); for (int32_t i = 0; i != num_vertices; ++i) - values[i] = FreeSurfer::get_BE (in); + (*this)[i] = FreeSurfer::get_BE (in); } else { const int32_t num_vertices = magic_number; - if (num_vertices != mesh.num_vertices()) + if (size_t(num_vertices) != mesh.num_vertices()) throw Exception ("Error opening file \"" + path + "\" as Freesurfer curv file: Incorrect number of vertices"); const int32_t num_faces = FreeSurfer::get_int24_BE (in); - if (num_faces != mesh.num_polygons()) + if (size_t(num_faces) != mesh.num_polygons()) throw Exception ("Error opening file \"" + path + "\" as Freesurfer curv file: Incorrect number of polygons"); - values.resize (mesh.num_vertices()); + (*this).resize (mesh.num_vertices()); for (int32_t i = 0; i != num_vertices; ++i) - values[i] = 0.01 * FreeSurfer::get_BE (in); + (*this)[i] = 0.01 * FreeSurfer::get_BE (in); } if (!in.good()) throw Exception ("Error opening file \"" + path + "\" as Freesurfer curv file: Truncated file"); - - } - } } diff --git a/src/surface/scalar.h b/src/surface/scalar.h index 81a7f25400..6ef54314e2 100644 --- a/src/surface/scalar.h +++ b/src/surface/scalar.h @@ -26,51 +26,45 @@ namespace MR { - class Scalar { + class Scalar : public Eigen::Array { public: - typedef Eigen::Array vector_type; + typedef Eigen::Array Base; Scalar (const std::string&, const Mesh&); Scalar (const Scalar& that) = default; Scalar (Scalar&& that) : - values (std::move (that.values)) { } + Base (std::move (that)), + name (std::move (that.name)) { } Scalar() { } Scalar& operator= (Scalar&& that) { - values = std::move (that.values); + Base::operator= (std::move (that)); + name = std::move (that.name); return *this; } Scalar& operator= (const Scalar& that) { - values = that.values; + Base::operator= (that); + name = that.name; return *this; } void clear() { - values.clear(); + Base::resize(0); + name.clear(); } void save (const std::string&) const; - size_t num_values() const { return values.size(); } - const std::string& get_name() const { return name; } void set_name (const std::string& s) { name = s; } - default_type value (const size_t i) const { assert (i < values.size()); return values[i]; } - - const vector_type& get_values() const { return values; } - - - protected: - vector_type values; - private: std::string name; From 923c78b2f57653c79cf729eb4b4463113be75f32 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 4 Jul 2016 12:50:06 +1000 Subject: [PATCH 019/723] Additional details on Surface::Scalar import error --- src/surface/scalar.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/surface/scalar.cpp b/src/surface/scalar.cpp index 4823c994bd..849d3e2938 100644 --- a/src/surface/scalar.cpp +++ b/src/surface/scalar.cpp @@ -70,7 +70,7 @@ namespace MR const int32_t index = FreeSurfer::get_int24_BE (in); const float value = FreeSurfer::get_BE (in); if (size_t(index) >= mesh.num_vertices()) - throw Exception ("Error opening file \"" + path + "\" as FreeSurfer w-file: invalid vertex index"); + throw Exception ("Error opening file \"" + path + "\" as FreeSurfer w-file: invalid vertex index (" + str(index) + ")"); if (!in.good()) throw Exception ("Error opening file \"" + path + "\" as FreeSurfer w-file: truncated file"); (*this)[index] = value; @@ -90,11 +90,11 @@ namespace MR const int32_t num_vertices = FreeSurfer::get_BE (in); if (size_t(num_vertices) != mesh.num_vertices()) - throw Exception ("Error opening file \"" + path + "\" as Freesurfer curv file: Incorrect number of vertices"); + throw Exception ("Error opening file \"" + path + "\" as Freesurfer curv file: Incorrect number of vertices (" + str(num_vertices) + ")"); const int32_t num_faces = FreeSurfer::get_BE (in); if (size_t(num_faces) != mesh.num_polygons()) - throw Exception ("Error opening file \"" + path + "\" as Freesurfer curv file: Incorrect number of polygons"); + throw Exception ("Error opening file \"" + path + "\" as Freesurfer curv file: Incorrect number of polygons (" + str(num_faces) + ")"); const int32_t vals_per_vertex = FreeSurfer::get_BE (in); if (vals_per_vertex != 1) From ddd0c935687dce6fb13e55895176739a0b12b3a0 Mon Sep 17 00:00:00 2001 From: rtabbara Date: Tue, 5 Jul 2016 14:43:36 +1000 Subject: [PATCH 020/723] First-pass prototype of fod2fixel using new fixel folder format as mentioned in #620 * Testing out new interface * For now, aim is to make changes dead-simple relative to original implementation --- cmd/fod2fixel.cpp | 266 +++++++++++++++++++++++++++---------- lib/fixel_format/helpers.h | 66 +++++++++ lib/fixel_format/keys.h | 29 ++++ 3 files changed, 292 insertions(+), 69 deletions(-) create mode 100644 lib/fixel_format/helpers.h create mode 100644 lib/fixel_format/keys.h diff --git a/cmd/fod2fixel.cpp b/cmd/fod2fixel.cpp index c51c140e9b..1d1b341cca 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -19,9 +19,8 @@ #include "image.h" -#include "sparse/fixel_metric.h" -#include "sparse/keys.h" -#include "sparse/image.h" +#include "fixel_format/keys.h" +#include "fixel_format/helpers.h" #include "math/SH.h" @@ -30,6 +29,9 @@ #include "dwi/fmls.h" #include "dwi/directions/set.h" +#include "gui/dialog/progress.h" +#include "file/path.h" + @@ -40,11 +42,15 @@ using namespace App; -using Sparse::FixelMetric; - +const OptionGroup OutputOptions = OptionGroup ("Metric values for fixel-based sparse output images") + + Option ("index", + "store the index file") + + Argument ("image").type_image_out() -const OptionGroup OutputOptions = OptionGroup ("Metric values for fixel-based sparse output images") + + Option ("dir", + "store the fixel directions") + + Argument ("image").type_image_out() + Option ("afd", "store the total Apparent Fibre Density per fixel (integral of FOD lobe)") @@ -81,7 +87,8 @@ void usage () "Neuroimage, 2012, 15;59(4), 3976-94."; ARGUMENTS - + Argument ("fod", "the input fod image.").type_image_in (); + + Argument ("fod", "the input fod image.").type_image_in () + + Argument ("fixel_folder", "the output fixel folder").type_text(); OPTIONS @@ -107,104 +114,204 @@ class Segmented_FOD_receiver public: Segmented_FOD_receiver (const Header& header) : - H (header) + H (header), n_fixels (0), output_index_file (true) { - H.set_ndim (3); - H.datatype() = DataType::UInt64; - H.datatype().set_byte_order_native(); - H.keyval()[Sparse::name_key] = str(typeid(FixelMetric).name()); - H.keyval()[Sparse::size_key] = str(sizeof(FixelMetric)); } + void commit (); + + void set_output_index_file (bool flag) { output_index_file = flag; } + void set_fixel_folder_output (const std::string& path) { fixel_folder_path = path; } + void set_index_output (const std::string& path) { index_path = path; } + std::string get_index_output () { return index_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_output (const std::string& path) { peak_path = path; } + void set_disp_output (const std::string& path) { disp_path = path; } - void set_afd_output (const std::string&); - void set_peak_output (const std::string&); - void set_disp_output (const std::string&); size_t num_outputs() const; bool operator() (const FOD_lobes&); - private: - Header H; + template void primitive_commit (); + template Image commit_index_file (); - // These must be stored using pointers as Sparse::Image - // uses class constructors rather than static functions; - // the Sparse::Image class should be modified - std::unique_ptr> afd, peak, disp; + Header H; + std::string fixel_folder_path, index_path, dir_path, afd_path, peak_path, disp_path; + std::vector lobes; + uint64_t n_fixels; + bool output_index_file; }; - -void Segmented_FOD_receiver::set_afd_output (const std::string& path) +size_t Segmented_FOD_receiver::num_outputs() const { - assert (!afd); - afd.reset (new Sparse::Image (path, H)); + size_t count = output_index_file ? 1 : 0; + if (dir_path.size()) ++count; + if (afd_path.size()) ++count; + if (peak_path.size()) ++count; + if (disp_path.size()) ++count; + return count; } -void Segmented_FOD_receiver::set_peak_output (const std::string& path) + + + +bool Segmented_FOD_receiver::operator() (const FOD_lobes& in) { - assert (!peak); - peak.reset (new Sparse::Image (path, H)); + + if (size_t n = in.size()) { + lobes.emplace_back (in); + n_fixels += n; + } + + return true; } -void Segmented_FOD_receiver::set_disp_output (const std::string& path) + + +void Segmented_FOD_receiver::commit () { - assert (!disp); - disp.reset (new Sparse::Image (path, H)); + if (!lobes.size() || !n_fixels || !num_outputs()) + return; + + if (n_fixels < (uint64_t)std::numeric_limits::max ()) + primitive_commit (); + else + primitive_commit (); } -size_t Segmented_FOD_receiver::num_outputs() const + + +template +void Segmented_FOD_receiver::primitive_commit () { - size_t count = 0; - if (afd) ++count; - if (peak) ++count; - if (disp) ++count; - return count; -} + using DataImage = Image; + using IndexImage = Image; + + const auto index_filepath = Path::join (fixel_folder_path, index_path); + + std::unique_ptr> index_image (nullptr); + std::unique_ptr dir_image (nullptr); + std::unique_ptr afd_image (nullptr); + std::unique_ptr peak_image (nullptr); + std::unique_ptr disp_image (nullptr); + + + if (output_index_file) { + auto index_header (H); + index_header.keyval()[FixelFormat::n_fixels_key] = str(n_fixels); + index_header.set_ndim (4); + index_header.size (3) = 2; + index_header.datatype () = DataType::from (); + index_header.datatype ().set_byte_order_native (); + index_image = std::unique_ptr (new IndexImage (IndexImage::create (index_filepath, index_header))); + } else { + index_image = std::unique_ptr (new IndexImage (IndexImage::open (index_filepath))); + } + auto fixel_data_header (H); + fixel_data_header.set_ndim (3); + fixel_data_header.size (0) = n_fixels; + fixel_data_header.size (2) = 1; + fixel_data_header.datatype () = DataType::Float32; + fixel_data_header.datatype ().set_byte_order_native(); + + if (dir_path.size ()) { + auto dir_header (fixel_data_header); + dir_header.size (1) = 3; + dir_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_folder_path, dir_path), dir_header))); + dir_image->index (1) = 0; + FixelFormat::check_fixel_size (*index_image, *dir_image); + } + + if (afd_path.size ()) { + auto afd_header (fixel_data_header); + afd_header.size (1) = 1; + afd_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_folder_path, afd_path), afd_header))); + afd_image->index (1) = 0; + FixelFormat::check_fixel_size (*index_image, *afd_image); + } + + if (peak_path.size ()) { + auto peak_header (fixel_data_header); + peak_header.size (1) = 1; + peak_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_folder_path, peak_path), peak_header))); + peak_image->index (1) = 0; + FixelFormat::check_fixel_size (*index_image, *peak_image); + } + if (disp_path.size ()) { + auto disp_header (fixel_data_header); + disp_header.size (1) = 1; + disp_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_folder_path, disp_path), disp_header))); + disp_image->index (1) = 0; + FixelFormat::check_fixel_size (*index_image, *disp_image); + } + size_t offset (0), lobe_index (0); + const size_t nlobes (lobes.size ()); + MR::ProgressBar progress("Generating fixel files", nlobes); + auto display_func = [&lobe_index, nlobes, &offset]() { + return str (lobe_index) + " of " + str (nlobes) + " FOD lobes read. " + str (offset) + " fixels found."; + }; + for (const auto& vox_fixels : lobes) { + size_t n_vox_fixels = vox_fixels.size(); -bool Segmented_FOD_receiver::operator() (const FOD_lobes& in) -{ - if (!in.size()) - return true; - - if (afd) { - assign_pos_of (in.vox).to (*afd); - afd->value().set_size (in.size()); - for (size_t i = 0; i != in.size(); ++i) { - FixelMetric this_fixel (in[i].get_mean_dir(), in[i].get_integral(), in[i].get_integral()); - afd->value()[i] = this_fixel; + if (output_index_file) { + assign_pos_of (vox_fixels.vox).to (*index_image); + + index_image->index (3) = 0; + index_image->value () = n_vox_fixels; + + index_image->index (3) = 1; + index_image->value () = offset; } - } - if (peak) { - assign_pos_of (in.vox).to (*peak); - peak->value().set_size (in.size()); - for (size_t i = 0; i != in.size(); ++i) { - FixelMetric this_fixel (in[i].get_peak_dir(), in[i].get_integral(), in[i].get_peak_value()); - peak->value()[i] = this_fixel; + if (dir_image) { + for (size_t i = 0; i < n_vox_fixels; ++i) { + dir_image->index (0) = offset + i; + dir_image->row (1) = vox_fixels[i].get_mean_dir (); + } + } + + if (afd_image) { + for (size_t i = 0; i < n_vox_fixels; ++i) { + afd_image->index (0) = offset + i; + afd_image->value () = vox_fixels[i].get_integral (); + } + } + + if (peak_image) { + for (size_t i = 0; i < n_vox_fixels; ++i) { + peak_image->index (0) = offset + i; + peak_image->value () = vox_fixels[i].get_peak_value (); + } } - } - if (disp) { - assign_pos_of (in.vox).to (*disp); - disp->value().set_size (in.size()); - for (size_t i = 0; i != in.size(); ++i) { - FixelMetric this_fixel (in[i].get_mean_dir(), in[i].get_integral(), in[i].get_integral() / in[i].get_peak_value()); - disp->value()[i] = this_fixel; + if (disp_image) { + for (size_t i = 0; i < n_vox_fixels; ++i) { + disp_image->index (0) = offset + i; + disp_image->value () = vox_fixels[i].get_integral () / vox_fixels[i].get_peak_value (); + } } + + + offset += n_vox_fixels; + lobe_index ++; + + progress.update (display_func); } - return true; -} + progress.done (); + assert (offset == n_fixels); +} @@ -217,12 +324,18 @@ void run () Segmented_FOD_receiver receiver (H); + auto& fixel_folder_path = argument[1]; + receiver.set_fixel_folder_output (fixel_folder_path); + + static const std::string default_index_filename = "index.mif"; + receiver.set_index_output (default_index_filename); + auto - opt = get_options ("afd"); if (opt.size()) receiver.set_afd_output (opt[0][0]); + opt = get_options ("index"); if (opt.size()) receiver.set_index_output (opt[0][0]); + opt = get_options ("dir"); if (opt.size()) receiver.set_directions_output (opt[0][0]); + opt = get_options ("afd"); if (opt.size()) receiver.set_afd_output (opt[0][0]); opt = get_options ("peak"); if (opt.size()) receiver.set_peak_output (opt[0][0]); opt = get_options ("disp"); if (opt.size()) receiver.set_disp_output (opt[0][0]); - if (!receiver.num_outputs()) - throw Exception ("Nothing to do; please specify at least one output image type"); opt = get_options ("mask"); Image mask; @@ -232,6 +345,19 @@ void run () throw Exception ("Cannot use image \"" + str(opt[0][0]) + "\" as mask image; dimensions do not match FOD image"); } + + if (!Path::exists (fixel_folder_path)) + File::mkdir (fixel_folder_path); + else if (!Path::is_dir (fixel_folder_path)) + throw Exception (str(fixel_folder_path) + " is not a directory"); + else if (Path::exists (Path::join (fixel_folder_path, receiver.get_index_output ()))) { + receiver.set_output_index_file (false); + + if (!receiver.num_outputs ()) + throw Exception ("Nothing to do; please specify at least one output image type"); + } + + FMLS::FODQueueWriter writer (fod_data, mask); const DWI::Directions::Set dirs (5000); @@ -239,5 +365,7 @@ void run () load_fmls_thresholds (fmls); Thread::run_queue (writer, Thread::batch (SH_coefs()), Thread::multi (fmls), Thread::batch (FOD_lobes()), receiver); + + receiver.commit (); } diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h new file mode 100644 index 0000000000..75c14c932a --- /dev/null +++ b/lib/fixel_format/helpers.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __fixel_helpers_h__ +#define __fixel_helpers_h__ + +#include "formats/mrtrix_utils.h" +#include "fixel_format/keys.h" + +namespace MR +{ + namespace FixelFormat + { + template + inline bool is_index_file (const IndexFileHeader& in) + { + return in.keyval ().count (n_fixels_key); + } + + + template + inline void check_index_file (const IndexFileHeader& in) + { + if (!is_index_file (in)) + throw Exception (in.name () + " is not a valid fixel index file. Header key " + n_fixels_key + " not found"); + } + + + template + inline bool fixels_match (const IndexFileHeader& index_h, const DataFileHeader& data_h) + { + bool fixels_match (false); + + if (is_index_file (index_h)) { + fixels_match = std::stoul(index_h.keyval ().at (n_fixels_key)) == (unsigned long)data_h.size (0); + } + + return fixels_match; + } + + + template + inline void check_fixel_size (const IndexFileHeader& index_h, const DataFileHeader& data_h) + { + check_index_file (index_h); + + if (!fixels_match (index_h, data_h)) + throw Exception ("Fixel number mismatch between index file " + index_h.name() + " and data file " + data_h.name()); + } + } +} + +#endif + diff --git a/lib/fixel_format/keys.h b/lib/fixel_format/keys.h new file mode 100644 index 0000000000..a560c29d91 --- /dev/null +++ b/lib/fixel_format/keys.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __fixel_keys_h__ +#define __fixel_keys_h__ + +#include + +namespace MR +{ + namespace FixelFormat + { + const std::string n_fixels_key ("nfixels"); + } +} + +#endif From 74cfb7400bae28da13992ced81d151dd61af8587 Mon Sep 17 00:00:00 2001 From: rtabbara Date: Thu, 7 Jul 2016 16:16:45 +1000 Subject: [PATCH 021/723] Replace fixelthreshold with fixelcrop command --- cmd/fixelcrop.cpp | 127 +++++++++++++++++++++++++++++++++++++ cmd/fixelthreshold.cpp | 101 ----------------------------- lib/fixel_format/helpers.h | 78 +++++++++++++++++++---- lib/fixel_format/keys.h | 1 + 4 files changed, 195 insertions(+), 112 deletions(-) create mode 100644 cmd/fixelcrop.cpp delete mode 100644 cmd/fixelthreshold.cpp diff --git a/cmd/fixelcrop.cpp b/cmd/fixelcrop.cpp new file mode 100644 index 0000000000..12b060f609 --- /dev/null +++ b/cmd/fixelcrop.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + +#include "command.h" +#include "progressbar.h" +#include "algo/loop.h" + +#include "image.h" + +#include "fixel_format/helpers.h" +#include "fixel_format/keys.h" + +using namespace MR; +using namespace App; + + +void usage () +{ + AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; + + DESCRIPTION + + "Crop a fixel index image along with corresponding fixel data images"; + + ARGUMENTS + + Argument ("fixel_in", "the input fixel folder").type_text () + + Argument ("fixel_out", "the output fixel folder").type_text (); + + OPTIONS + + Option ("mask", "the threshold mask").required () + + Argument ("data_image").type_image_in () + + Option ("data", "specify the list of fixel data images to be cropped relative to the input fixel folder ").allow_multiple () + + Argument ("data_images").type_image_in (); +} + + + +void run () +{ + const auto fixel_folder = argument[0]; + FixelFormat::check_fixel_folder (fixel_folder); + + const auto out_fixel_folder = argument[1]; + auto index_image = FixelFormat::find_index_header (fixel_folder).get_image (); + + auto opt = get_options ("mask"); + auto mask_image = Image::open (std::string (opt[0][0])); + + FixelFormat::check_fixel_size (index_image, mask_image); + FixelFormat::check_fixel_folder (out_fixel_folder, true); + + Header out_header = Header (index_image); + size_t total_nfixels = std::stoul (out_header.keyval ()[FixelFormat::n_fixels_key]); + + // We need to do a first pass fo the mask image to determine the cropped images' properties + for (auto l = Loop (0) (mask_image); l; ++l) { + if (!mask_image.value ()) + total_nfixels --; + } + + out_header.keyval ()[FixelFormat::n_fixels_key] = str (total_nfixels); + auto index_image_basename = Path::basename (index_image.name ()); + auto out_index_image = Image::create (Path::join (out_fixel_folder, index_image_basename), out_header); + + // Crop index images + mask_image.index (1) = 0; + for (auto l = Loop ("cropping index image: " + index_image_basename, 0, 3) (index_image, out_index_image); l; ++l) { + index_image.index (3) = 0; + size_t nfixels = index_image.value (); + + index_image.index (3) = 1; + size_t offset = index_image.value (); + + for (size_t i = 0, N = nfixels; i < N; ++i) { + mask_image.index (0) = offset + i; + if (!mask_image.value ()) + nfixels--; + } + + out_index_image.index (3) = 0; + out_index_image.value () = nfixels; + + out_index_image.index (3) = 1; + out_index_image.value () = offset; + } + + // Crop data images + opt = get_options ("data"); + + for (size_t n = 0, N = opt.size (); n < N; n++) { + const auto data_file = opt[n][0]; + const auto data_file_path = Path::join (fixel_folder, data_file); + + Header header = Header::open (data_file_path); + auto in_data = header.get_image (); + + check_dimensions (in_data, mask_image, {0, 2}); + + header.size (0) = total_nfixels; + auto out_data = Image::create (Path::join (out_fixel_folder, data_file), header); + + size_t offset(0); + + for (auto l = Loop ("cropping data: " + data_file, 0) (mask_image, in_data); l; ++l) { + if (mask_image.value ()) { + out_data.index (1) = offset; + out_data.row (1) = in_data.row (1); + offset++; + } + } + + } + +} + diff --git a/cmd/fixelthreshold.cpp b/cmd/fixelthreshold.cpp deleted file mode 100644 index 487a72276f..0000000000 --- a/cmd/fixelthreshold.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - - -#include "command.h" -#include "progressbar.h" -#include "algo/loop.h" - -#include "image.h" - -#include "sparse/fixel_metric.h" -#include "sparse/keys.h" -#include "sparse/image.h" - -using namespace MR; -using namespace App; - -using Sparse::FixelMetric; - -void usage () -{ - AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; - - DESCRIPTION - + "Threshold the values in a fixel image"; - - ARGUMENTS - + Argument ("fixel_in", "the input fixel image.").type_image_in () - + Argument ("threshold", "the input threshold").type_float() - + Argument ("fixel_out", "the output fixel image").type_image_out (); - - OPTIONS - + Option ("crop", "remove fixels that fall below threshold (instead of assigning their value to zero or one)") - + Option ("invert", "invert the output image (i.e. below threshold fixels are included instead)"); - -} - - - -void run () -{ - auto input_header = Header::open (argument[0]); - Sparse::Image input (argument[0]); - - float threshold = argument[1]; - - Sparse::Image output (argument[2], input_header); - - auto opt = get_options("crop"); - const bool invert = get_options("invert").size(); - - for (auto i = Loop ("thresholding fixel image", input) (input, output); i; ++i) { - if (opt.size()) { - size_t fixel_count = 0; - for (size_t f = 0; f != input.value().size(); ++f) { - if (invert) { - if (input.value()[f].value < threshold) - fixel_count++; - } else { - if (input.value()[f].value > threshold) - fixel_count++; - } - } - output.value().set_size (fixel_count); - fixel_count = 0; - for (size_t f = 0; f != input.value().size(); ++f) { - if (invert) { - if (input.value()[f].value < threshold) - output.value()[fixel_count++] = input.value()[f]; - } else { - if (input.value()[f].value > threshold) - output.value()[fixel_count++] = input.value()[f]; - } - } - } else { - output.value().set_size (input.value().size()); - for (size_t f = 0; f != input.value().size(); ++f) { - output.value()[f] = input.value()[f]; - if (input.value()[f].value > threshold) { - output.value()[f].value = (invert) ? 0.0 : 1.0; - } else { - output.value()[f].value = (invert) ? 1.0 : 0.0; - } - } - } - } - -} - diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index 75c14c932a..0841ef8f12 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -23,27 +23,42 @@ namespace MR { namespace FixelFormat { - template - inline bool is_index_file (const IndexFileHeader& in) + template + inline bool is_index_image (const IndexFileHeader& in) { return in.keyval ().count (n_fixels_key); } - template - inline void check_index_file (const IndexFileHeader& in) + template + inline void check_index_image (const IndexFileHeader& in) { - if (!is_index_file (in)) - throw Exception (in.name () + " is not a valid fixel index file. Header key " + n_fixels_key + " not found"); + if (!is_index_image (in)) + throw Exception (in.name () + " is not a valid fixel index image. Header key " + n_fixels_key + " not found"); } - template + template + inline bool is_data_image (const DataFileHeader& in) + { + return in.ndim () == 3 && in.size (2) == 1; + } + + + template + inline void check_data_image (const DataFileHeader& in) + { + if (!is_data_image (in)) + throw Exception (in.name () + " is not a valid fixel data image. Expected a 3-dimensionl image of size n x m x 1"); + } + + + template inline bool fixels_match (const IndexFileHeader& index_h, const DataFileHeader& data_h) { bool fixels_match (false); - if (is_index_file (index_h)) { + if (is_index_image (index_h)) { fixels_match = std::stoul(index_h.keyval ().at (n_fixels_key)) == (unsigned long)data_h.size (0); } @@ -51,14 +66,55 @@ namespace MR } - template + template inline void check_fixel_size (const IndexFileHeader& index_h, const DataFileHeader& data_h) { - check_index_file (index_h); + check_index_image (index_h); + check_data_image (data_h); if (!fixels_match (index_h, data_h)) - throw Exception ("Fixel number mismatch between index file " + index_h.name() + " and data file " + data_h.name()); + throw Exception ("Fixel number mismatch between index image " + index_h.name() + " and data image " + data_h.name()); } + + + inline bool check_fixel_folder (const std::string &path, bool create_if_missing = false) + { + bool exists (true); + + if (!(exists = Path::exists (path))) { + if (create_if_missing) File::mkdir (path); + else throw Exception ("Fixel directory " + str(path) + " does not exist"); + } + else if (!Path::is_dir (path)) + throw Exception (str(path) + " is not a directory"); + + return exists; + } + + + inline Header find_index_header (const std::string &fixel_folder_path) + { + bool index_found (false); + Header H; + check_fixel_folder (fixel_folder_path); + + auto dir_walker = Path::Dir (fixel_folder_path); + std::string fname; + while ((fname = dir_walker.read_name ()).size ()) { + auto full_path = Path::join (fixel_folder_path, fname); + if (Path::has_suffix (fname, FixelFormat::supported_fixel_formats) && is_index_image (H = Header::open (full_path))) { + index_found = true; + break; + } + } + + if (!index_found) + throw Exception ("Could not find index image in directory " + fixel_folder_path); + + return H; + } + + } } diff --git a/lib/fixel_format/keys.h b/lib/fixel_format/keys.h index a560c29d91..643ba898e8 100644 --- a/lib/fixel_format/keys.h +++ b/lib/fixel_format/keys.h @@ -23,6 +23,7 @@ namespace MR namespace FixelFormat { const std::string n_fixels_key ("nfixels"); + const std::initializer_list supported_fixel_formats { ".mif", ".nii" }; } } From a5940930f87a32653c26aa9461915f740f089ca1 Mon Sep 17 00:00:00 2001 From: rtabbara Date: Thu, 7 Jul 2016 16:18:15 +1000 Subject: [PATCH 022/723] Remove unused parameter to avoid compiler warnings --- lib/thread_queue.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thread_queue.h b/lib/thread_queue.h index 079475d97d..8301d3ced8 100644 --- a/lib/thread_queue.h +++ b/lib/thread_queue.h @@ -832,7 +832,7 @@ namespace MR * (defaults to MRTRIX_QUEUE_DEFAULT_BATCH_SIZE). * \sa Thread::run_queue() */ template - inline __Batch batch (const Item& object, size_t number = MRTRIX_QUEUE_DEFAULT_BATCH_SIZE) + inline __Batch batch (const Item&, size_t number = MRTRIX_QUEUE_DEFAULT_BATCH_SIZE) { return __Batch (number); } From 0a70cd701795ac19f6721c2fe1a8d9c4342e05bb Mon Sep 17 00:00:00 2001 From: rtabbara Date: Thu, 7 Jul 2016 16:19:37 +1000 Subject: [PATCH 023/723] fod2fixel: Cleaning up --- cmd/fod2fixel.cpp | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/cmd/fod2fixel.cpp b/cmd/fod2fixel.cpp index e092a81701..c6ce204de6 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -135,7 +135,6 @@ class Segmented_FOD_receiver private: - template void primitive_commit (); Header H; std::string fixel_folder_path, index_path, dir_path, afd_path, peak_path, disp_path; @@ -177,23 +176,12 @@ void Segmented_FOD_receiver::commit () if (!lobes.size() || !n_fixels || !num_outputs()) return; - if (n_fixels < (uint64_t)std::numeric_limits::max ()) - primitive_commit (); - else - primitive_commit (); -} - - - -template -void Segmented_FOD_receiver::primitive_commit () -{ using DataImage = Image; - using IndexImage = Image; + using IndexImage = Image; const auto index_filepath = Path::join (fixel_folder_path, index_path); - std::unique_ptr> index_image (nullptr); + std::unique_ptr index_image (nullptr); std::unique_ptr dir_image (nullptr); std::unique_ptr afd_image (nullptr); std::unique_ptr peak_image (nullptr); @@ -205,7 +193,7 @@ void Segmented_FOD_receiver::primitive_commit () index_header.keyval()[FixelFormat::n_fixels_key] = str(n_fixels); index_header.ndim () = 4; index_header.size (3) = 2; - index_header.datatype () = DataType::from (); + index_header.datatype () = DataType::from (); index_header.datatype ().set_byte_order_native (); index_image = std::unique_ptr (new IndexImage (IndexImage::create (index_filepath, index_header))); } else { @@ -252,12 +240,7 @@ void Segmented_FOD_receiver::primitive_commit () } size_t offset (0), lobe_index (0); - const size_t nlobes (lobes.size ()); - MR::ProgressBar progress("Generating fixel files", nlobes); - auto display_func = [&lobe_index, nlobes, &offset]() { - return str (lobe_index) + " of " + str (nlobes) + " FOD lobes read. " + str (offset) + " fixels found."; - }; for (const auto& vox_fixels : lobes) { size_t n_vox_fixels = vox_fixels.size(); @@ -303,12 +286,8 @@ void Segmented_FOD_receiver::primitive_commit () offset += n_vox_fixels; lobe_index ++; - - progress.update (display_func); } - progress.done (); - assert (offset == n_fixels); } From 0f97b4530f724f9686bcc5b546600eabff4318a1 Mon Sep 17 00:00:00 2001 From: rtabbara Date: Thu, 7 Jul 2016 16:31:09 +1000 Subject: [PATCH 024/723] fixelcrop: Cleaning up cmd comments --- cmd/fixelcrop.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/cmd/fixelcrop.cpp b/cmd/fixelcrop.cpp index 12b060f609..82b2273eb2 100644 --- a/cmd/fixelcrop.cpp +++ b/cmd/fixelcrop.cpp @@ -35,14 +35,15 @@ void usage () + "Crop a fixel index image along with corresponding fixel data images"; ARGUMENTS - + Argument ("fixel_in", "the input fixel folder").type_text () - + Argument ("fixel_out", "the output fixel folder").type_text (); + + Argument ("dir_in", "the input fixel folder").type_text () + + Argument ("dir_out", "the output fixel folder").type_text (); OPTIONS + Option ("mask", "the threshold mask").required () - + Argument ("data_image").type_image_in () - + Option ("data", "specify the list of fixel data images to be cropped relative to the input fixel folder ").allow_multiple () - + Argument ("data_images").type_image_in (); + + Argument ("image").type_image_in () + + Option ("data", "specify a fixel data image filename (relative to the input fixel folder) to " + "be cropped.").allow_multiple () + + Argument ("image").type_image_in (); } @@ -64,17 +65,17 @@ void run () Header out_header = Header (index_image); size_t total_nfixels = std::stoul (out_header.keyval ()[FixelFormat::n_fixels_key]); - // We need to do a first pass fo the mask image to determine the cropped images' properties + // We need to do a first pass fo the mask image to determine the cropped num. of fixels for (auto l = Loop (0) (mask_image); l; ++l) { if (!mask_image.value ()) total_nfixels --; } out_header.keyval ()[FixelFormat::n_fixels_key] = str (total_nfixels); - auto index_image_basename = Path::basename (index_image.name ()); + const auto index_image_basename = Path::basename (index_image.name ()); auto out_index_image = Image::create (Path::join (out_fixel_folder, index_image_basename), out_header); - // Crop index images + // Crop index image mask_image.index (1) = 0; for (auto l = Loop ("cropping index image: " + index_image_basename, 0, 3) (index_image, out_index_image); l; ++l) { index_image.index (3) = 0; From 9d616fa78ed3adea35232235cdba9a724a9e51d4 Mon Sep 17 00:00:00 2001 From: rtabbara Date: Mon, 11 Jul 2016 10:36:01 +1000 Subject: [PATCH 025/723] fixelcrop: Fixing comment typo --- cmd/fixelcrop.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/fixelcrop.cpp b/cmd/fixelcrop.cpp index 82b2273eb2..d1efcc2656 100644 --- a/cmd/fixelcrop.cpp +++ b/cmd/fixelcrop.cpp @@ -65,7 +65,7 @@ void run () Header out_header = Header (index_image); size_t total_nfixels = std::stoul (out_header.keyval ()[FixelFormat::n_fixels_key]); - // We need to do a first pass fo the mask image to determine the cropped num. of fixels + // We need to do a first pass of the mask image to determine the cropped num. of fixels for (auto l = Loop (0) (mask_image); l; ++l) { if (!mask_image.value ()) total_nfixels --; From 30fe58dacea52af80611b167a0499015e870f3e1 Mon Sep 17 00:00:00 2001 From: rtabbara Date: Tue, 12 Jul 2016 15:54:21 +1000 Subject: [PATCH 026/723] fod2fixel: Insisting that output fixel directory must be either new or empty * This ensures that the index-offset is consistent with any generated corresponding data images * Problem is that fod2fixel FOD segmenetation is multi-threaded -> cannot guarantee that the order of voxels processed is the same for multiple calls of fod2fixel * Therefore, by only allowing new folders, we can avoid (or at least make it harder for users) to have data-images whose offsets are mismatched with the index image --- cmd/fod2fixel.cpp | 52 +++++++++++++------------------------- lib/fixel_format/helpers.h | 25 ++++++++++++++++-- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/cmd/fod2fixel.cpp b/cmd/fod2fixel.cpp index c6ce204de6..2aa7e11ce9 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -114,13 +114,12 @@ class Segmented_FOD_receiver public: Segmented_FOD_receiver (const Header& header) : - H (header), n_fixels (0), output_index_file (true) + H (header), n_fixels (0) { } void commit (); - void set_output_index_file (bool flag) { output_index_file = flag; } void set_fixel_folder_output (const std::string& path) { fixel_folder_path = path; } void set_index_output (const std::string& path) { index_path = path; } std::string get_index_output () { return index_path; } @@ -140,14 +139,13 @@ class Segmented_FOD_receiver std::string fixel_folder_path, index_path, dir_path, afd_path, peak_path, disp_path; std::vector lobes; uint64_t n_fixels; - bool output_index_file; }; size_t Segmented_FOD_receiver::num_outputs() const { - size_t count = output_index_file ? 1 : 0; + size_t count = 1; if (dir_path.size()) ++count; if (afd_path.size()) ++count; if (peak_path.size()) ++count; @@ -187,18 +185,13 @@ void Segmented_FOD_receiver::commit () std::unique_ptr peak_image (nullptr); std::unique_ptr disp_image (nullptr); - - if (output_index_file) { - auto index_header (H); - index_header.keyval()[FixelFormat::n_fixels_key] = str(n_fixels); - index_header.ndim () = 4; - index_header.size (3) = 2; - index_header.datatype () = DataType::from (); - index_header.datatype ().set_byte_order_native (); - index_image = std::unique_ptr (new IndexImage (IndexImage::create (index_filepath, index_header))); - } else { - index_image = std::unique_ptr (new IndexImage (IndexImage::open (index_filepath))); - } + auto index_header (H); + index_header.keyval()[FixelFormat::n_fixels_key] = str(n_fixels); + index_header.ndim () = 4; + index_header.size (3) = 2; + index_header.datatype () = DataType::from (); + index_header.datatype ().set_byte_order_native (); + index_image = std::unique_ptr (new IndexImage (IndexImage::create (index_filepath, index_header))); auto fixel_data_header (H); fixel_data_header.ndim () = 3; @@ -245,15 +238,13 @@ void Segmented_FOD_receiver::commit () for (const auto& vox_fixels : lobes) { size_t n_vox_fixels = vox_fixels.size(); - if (output_index_file) { - 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->index (3) = 0; + index_image->value () = n_vox_fixels; - index_image->index (3) = 1; - index_image->value () = offset; - } + index_image->index (3) = 1; + index_image->value () = offset; if (dir_image) { for (size_t i = 0; i < n_vox_fixels; ++i) { @@ -283,7 +274,6 @@ void Segmented_FOD_receiver::commit () } } - offset += n_vox_fixels; lobe_index ++; } @@ -323,18 +313,10 @@ void run () throw Exception ("Cannot use image \"" + str(opt[0][0]) + "\" as mask image; dimensions do not match FOD image"); } + if (!receiver.num_outputs ()) + throw Exception ("Nothing to do; please specify at least one output image type"); - if (!Path::exists (fixel_folder_path)) - File::mkdir (fixel_folder_path); - else if (!Path::is_dir (fixel_folder_path)) - throw Exception (str(fixel_folder_path) + " is not a directory"); - else if (Path::exists (Path::join (fixel_folder_path, receiver.get_index_output ()))) { - receiver.set_output_index_file (false); - - if (!receiver.num_outputs ()) - throw Exception ("Nothing to do; please specify at least one output image type"); - } - + FixelFormat::check_fixel_folder (fixel_folder_path, true, true); FMLS::FODQueueWriter writer (fod_data, mask); diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index 0841ef8f12..9d3b77fac5 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -77,7 +77,7 @@ namespace MR } - inline bool check_fixel_folder (const std::string &path, bool create_if_missing = false) + inline void check_fixel_folder (const std::string &path, bool create_if_missing = false, bool check_if_empty = false) { bool exists (true); @@ -88,7 +88,8 @@ namespace MR else if (!Path::is_dir (path)) throw Exception (str(path) + " is not a directory"); - return exists; + if (check_if_empty && Path::Dir (path).read_name ().size () != 0) + throw Exception ("Expected fixel directory " + path + " to be empty."); } @@ -115,6 +116,26 @@ namespace MR } + inline std::vector
find_data_headers (const std::string &fixel_folder_path, const Header &index_header) + { + check_index_image (index_header); + + std::vector
data_headers; + + auto dir_walker = Path::Dir (fixel_folder_path); + std::string fname; + while ((fname = dir_walker.read_name ()).size ()) { + auto full_path = Path::join (fixel_folder_path, fname); + Header H; + if (Path::has_suffix (fname, FixelFormat::supported_fixel_formats) + && is_data_image (H = Header::open (full_path)) + && fixels_match (index_header, H)) { + data_headers.emplace_back (std::move (H)); + } + } + + return data_headers; + } } } From e3aa2daf99455642486b3bb4723df97a0f2e7ef5 Mon Sep 17 00:00:00 2001 From: rtabbara Date: Tue, 12 Jul 2016 16:03:35 +1000 Subject: [PATCH 027/723] fod2fixel: Create default directions file if not explicitly specified by user * Warn user in this instance when creating default directions file --- cmd/fod2fixel.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cmd/fod2fixel.cpp b/cmd/fod2fixel.cpp index 2aa7e11ce9..14a908906e 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -122,7 +122,6 @@ class Segmented_FOD_receiver void set_fixel_folder_output (const std::string& path) { fixel_folder_path = path; } void set_index_output (const std::string& path) { index_path = path; } - std::string get_index_output () { return index_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_output (const std::string& path) { peak_path = path; } @@ -296,11 +295,15 @@ void run () receiver.set_fixel_folder_output (fixel_folder_path); static const std::string default_index_filename = "index.mif"; + static const std::string default_directions_filename = "directions.mif"; receiver.set_index_output (default_index_filename); + receiver.set_directions_output (default_directions_filename); + + bool set_directions_file (false); auto opt = get_options ("index"); if (opt.size()) receiver.set_index_output (opt[0][0]); - opt = get_options ("dir"); if (opt.size()) receiver.set_directions_output (opt[0][0]); + opt = get_options ("dir"); if (opt.size()) { receiver.set_directions_output (opt[0][0]); set_directions_file = true; } opt = get_options ("afd"); if (opt.size()) receiver.set_afd_output (opt[0][0]); opt = get_options ("peak"); if (opt.size()) receiver.set_peak_output (opt[0][0]); opt = get_options ("disp"); if (opt.size()) receiver.set_disp_output (opt[0][0]); @@ -318,6 +321,9 @@ void run () FixelFormat::check_fixel_folder (fixel_folder_path, true, true); + if (!set_directions_file) + WARN ("No explicit directions image filename specified. Generating default " + default_directions_filename); + FMLS::FODQueueWriter writer (fod_data, mask); const DWI::Directions::Set dirs (1281); From 9898f53b98fa716743bd73fffa17dd0c03aa91cb Mon Sep 17 00:00:00 2001 From: rtabbara Date: Wed, 13 Jul 2016 15:48:43 +1000 Subject: [PATCH 028/723] Vector tool: First pass at allowing fixel-folders to be rendered * Update scaling-by drop-down menu to allow for multiple different value-type rendering * Still need to update gui to allow for selection of different threshold data images --- lib/fixel_format/helpers.h | 34 ++--- src/gui/mrview/tool/fixel.cpp | 218 ++++++++++++++++++++++++++++----- src/gui/mrview/tool/fixel.h | 81 ++++++++++-- src/gui/mrview/tool/vector.cpp | 40 +++--- 4 files changed, 303 insertions(+), 70 deletions(-) diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index 9d3b77fac5..1574be3b08 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -21,40 +21,43 @@ namespace MR { + class InvalidFixelDirectoryException : public Exception + { + public: + InvalidFixelDirectoryException (const std::string& msg) : Exception(msg) {} + InvalidFixelDirectoryException (const Exception& previous_exception, const std::string& msg) + : Exception(previous_exception, msg) {} + }; + namespace FixelFormat { - template - inline bool is_index_image (const IndexFileHeader& in) + inline bool is_index_image (const Header& in) { return in.keyval ().count (n_fixels_key); } - template - inline void check_index_image (const IndexFileHeader& in) + inline void check_index_image (const Header& in) { if (!is_index_image (in)) - throw Exception (in.name () + " is not a valid fixel index image. Header key " + n_fixels_key + " not found"); + throw InvalidImageException (in.name () + " is not a valid fixel index image. Header key " + n_fixels_key + " not found"); } - template - inline bool is_data_image (const DataFileHeader& in) + inline bool is_data_image (const Header& in) { return in.ndim () == 3 && in.size (2) == 1; } - template - inline void check_data_image (const DataFileHeader& in) + inline void check_data_image (const Header& in) { if (!is_data_image (in)) - throw Exception (in.name () + " is not a valid fixel data image. Expected a 3-dimensionl image of size n x m x 1"); + throw InvalidImageException (in.name () + " is not a valid fixel data image. Expected a 3-dimensionl image of size n x m x 1"); } - template - inline bool fixels_match (const IndexFileHeader& index_h, const DataFileHeader& data_h) + inline bool fixels_match (const Header& index_h, const Header& data_h) { bool fixels_match (false); @@ -66,14 +69,13 @@ namespace MR } - template - inline void check_fixel_size (const IndexFileHeader& index_h, const DataFileHeader& data_h) + inline void check_fixel_size (const Header& index_h, const Header& data_h) { check_index_image (index_h); check_data_image (data_h); if (!fixels_match (index_h, data_h)) - throw Exception ("Fixel number mismatch between index image " + index_h.name() + " and data image " + data_h.name()); + throw InvalidImageException ("Fixel number mismatch between index image " + index_h.name() + " and data image " + data_h.name()); } @@ -110,7 +112,7 @@ namespace MR } if (!index_found) - throw Exception ("Could not find index image in directory " + fixel_folder_path); + throw InvalidFixelDirectoryException ("Could not find index image in directory " + fixel_folder_path); return H; } diff --git a/src/gui/mrview/tool/fixel.cpp b/src/gui/mrview/tool/fixel.cpp index 9fa08b3bd8..a572db314f 100644 --- a/src/gui/mrview/tool/fixel.cpp +++ b/src/gui/mrview/tool/fixel.cpp @@ -33,11 +33,11 @@ namespace MR slice_fixel_indices (3), slice_fixel_sizes (3), slice_fixel_counts (3), + length_type (Unity), fixel_tool (fixel_tool), voxel_size_length_multipler (1.f), user_line_length_multiplier (1.f), line_thickness (0.0015f), - length_type (Unity), colour_type (CValue) { set_allowed_features (true, true, false); @@ -185,7 +185,9 @@ namespace MR const AbstractFixel& fixel (dynamic_cast (object)); if (color_type != fixel.colour_type) return true; - if (length_type != fixel.length_type) + else if (length_type != fixel.length_type) + return true; + else if (fixel.internal_buffers_dirty ()) return true; return Displayable::Shader::need_update (object); } @@ -207,6 +209,8 @@ namespace MR start (fixel_shader); projection.set (fixel_shader); + update_image_buffer (); + gl::Uniform1f (gl::GetUniformLocation (fixel_shader, "length_mult"), voxel_size_length_multipler * user_line_length_multiplier); gl::Uniform1f (gl::GetUniformLocation (fixel_shader, "line_thickness"), line_thickness); @@ -313,9 +317,9 @@ namespace MR // two or more points in our regular grid may correspond to the same nearest voxel for(const GLsizei index : voxel_indices) { regular_grid_buffer_pos.push_back (scanner_pos); - regular_grid_buffer_dir.push_back (buffer_dir[index]); - regular_grid_buffer_val.push_back (buffer_val[2 * index]); - regular_grid_buffer_val.push_back (buffer_val[(2 * index) + 1]); + regular_grid_buffer_dir.push_back ((*buffer_dir)[index]); + regular_grid_buffer_val.push_back ((*buffer_val)[2 * index]); + regular_grid_buffer_val.push_back ((*buffer_val)[(2 * index) + 1]); } } } @@ -358,35 +362,49 @@ namespace MR load_image_buffer (); - regular_grid_buffer_pos = std::vector (buffer_pos.size()); + regular_grid_buffer_pos = std::vector (buffer_pos->size ()); + + regular_grid_vao.gen (); + + regular_grid_vertex_buffer.gen (); + regular_grid_dir_buffer.gen (); + regular_grid_val_buffer.gen (); - regular_grid_vao.gen(); + vertex_array_object.gen (); + vertex_array_object.bind (); - regular_grid_vertex_buffer.gen(); - regular_grid_dir_buffer.gen(); - regular_grid_val_buffer.gen(); + vertex_buffer.gen (); + direction_buffer.gen (); + value_buffer.gen (); - vertex_array_object.gen(); - vertex_array_object.bind(); + reload_dir_and_value_buffers (); + + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + } + + + void AbstractFixel::reload_dir_and_value_buffers () + { + MRView::GrabContext context; + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + + vertex_array_object.bind (); // voxel centres - vertex_buffer.gen(); vertex_buffer.bind (gl::ARRAY_BUFFER); - gl::BufferData (gl::ARRAY_BUFFER, buffer_pos.size() * sizeof(Eigen::Vector3f), &buffer_pos[0][0], gl::STATIC_DRAW); + gl::BufferData (gl::ARRAY_BUFFER, buffer_pos->size () * sizeof(Eigen::Vector3f), &(*buffer_pos)[0][0], gl::STATIC_DRAW); gl::EnableVertexAttribArray (0); gl::VertexAttribPointer (0, 3, gl::FLOAT, gl::FALSE_, 0, (void*)0); // fixel directions - direction_buffer.gen(); direction_buffer.bind (gl::ARRAY_BUFFER); - gl::BufferData (gl::ARRAY_BUFFER, buffer_dir.size() * sizeof(Eigen::Vector3f), &buffer_dir[0][0], gl::STATIC_DRAW); + gl::BufferData (gl::ARRAY_BUFFER, buffer_dir->size () * sizeof(Eigen::Vector3f), &(*buffer_dir)[0][0], gl::STATIC_DRAW); gl::EnableVertexAttribArray (1); gl::VertexAttribPointer (1, 3, gl::FLOAT, gl::FALSE_, 0, (void*)0); // fixel sizes and values - value_buffer.gen(); value_buffer.bind (gl::ARRAY_BUFFER); - gl::BufferData (gl::ARRAY_BUFFER, buffer_val.size() * sizeof(float), &buffer_val[0], gl::STATIC_DRAW); + gl::BufferData (gl::ARRAY_BUFFER, buffer_val->size () * sizeof(float), &(*buffer_val)[0], gl::STATIC_DRAW); gl::EnableVertexAttribArray (2); gl::VertexAttribPointer (2, 2, gl::FLOAT, gl::FALSE_, 0, (void*)0); @@ -416,12 +434,12 @@ namespace MR if (fixel_data->value()[f].value < value_min) value_min = fixel_data->value()[f].value; - buffer_pos.push_back (pos); - buffer_dir.push_back (fixel_data->value()[f].dir); - buffer_val.push_back (fixel_data->value()[f].size); - buffer_val.push_back (fixel_data->value()[f].value); + buffer_pos->push_back (pos); + buffer_dir->push_back (fixel_data->value()[f].dir); + buffer_val->push_back (fixel_data->value()[f].size); + buffer_val->push_back (fixel_data->value()[f].value); - GLint point_index = buffer_pos.size() - 1; + GLint point_index = buffer_pos->size() - 1; for (size_t axis = 0; axis < 3; ++axis) { slice_fixel_indices[axis][voxel[axis]].push_back (point_index); @@ -484,14 +502,14 @@ namespace MR value_min = std::min (value_min, length); value_max = std::max (value_max, length); - buffer_pos.push_back (pos); - buffer_dir.push_back (vector.normalized()); + buffer_pos->push_back (pos); + buffer_dir->push_back (vector.normalized()); // Use the vector length to represent both fixel amplitude and value - buffer_val.push_back (length); - buffer_val.push_back (length); + buffer_val->push_back (length); + buffer_val->push_back (length); - GLint point_index = buffer_pos.size() - 1; + GLint point_index = buffer_pos->size() - 1; for (size_t axis = 0; axis < 3; ++axis) { slice_fixel_indices[axis][voxel[axis]].push_back (point_index); @@ -508,6 +526,150 @@ namespace MR lessthan = value_min; } + + void FixelFolder::load_image_buffer() + { + for (size_t axis = 0; axis < 3; ++axis) { + slice_fixel_indices[axis].resize (fixel_data->size (axis)); + slice_fixel_sizes [axis].resize (fixel_data->size (axis)); + slice_fixel_counts [axis].resize (fixel_data->size (axis), 0); + } + + // Load fixel index image + for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { + + const std::array voxel {{ int(fixel_data->index(0)), int(fixel_data->index(1)), int(fixel_data->index(2)) }}; + Eigen::Vector3f pos { float(voxel[0]), float(voxel[1]), float(voxel[2]) }; + pos = transform.voxel2scanner.cast() * pos; + + fixel_data->index (3) = 0; + const size_t nfixels = fixel_data->value (); + + for (size_t f = 0; f < nfixels; ++f) { + + buffer_pos->push_back (pos); + + const GLint point_index = buffer_pos->size () - 1; + + for (size_t axis = 0; axis < 3; ++axis) { + slice_fixel_indices[axis][voxel[axis]].push_back (point_index); + slice_fixel_sizes [axis][voxel[axis]].push_back (1); + slice_fixel_counts [axis][voxel[axis]]++; + } + + voxel_to_indices_map[voxel].push_back (point_index); + } + } + + auto data_headers = FixelFormat::find_data_headers (Path::dirname (fixel_data->name ()), *fixel_data); + + // Load fixel data direction images + for (Header& header : data_headers) { + + if (header.size (1) != 3) continue; + + auto data_image = header.get_image ().with_direct_io (); + const auto data_key = Path::basename (data_image.name ()); + buffer_dir_dict[data_key]; + + data_image.index (1) = 0; + for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { + fixel_data->index (3) = 0; + const size_t nfixels = fixel_data->value (); + fixel_data->index (3) = 1; + const size_t offset = fixel_data->value (); + for (size_t f = 0; f < nfixels; ++f) { + data_image.index (0) = offset + f; + buffer_dir_dict[data_key].emplace_back (data_image.row (1)); + } + } + } + + if (!buffer_dir_dict.size()) + throw InvalidImageException ("Fixel index image " + fixel_data->name () + " has no associated directions file"); + + // Load fixel data value images + for (auto& header : data_headers) { + + if (header.size (1) != 1) continue; + + auto data_image = header.get_image (); + const auto data_key = Path::basename (header.name ()); + buffer_val_dict[data_key]; + std::pair min_max = { std::numeric_limits::max (), std::numeric_limits::min () }; + + value_types.push_back (data_key); + + data_image.index (1) = 0; + for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { + fixel_data->index (3) = 0; + const size_t nfixels = fixel_data->value (); + fixel_data->index (3) = 1; + const size_t offset = fixel_data->value (); + + for (size_t f = 0; f < nfixels; ++f) { + data_image.index (0) = offset + f; + float value = data_image.value (); + buffer_val_dict[data_key].emplace_back (value); + // FIXME: Shader needs two values atm + buffer_val_dict[data_key].emplace_back (value); + min_max = { std::min (min_max.first, value), std::max (min_max.second, value) }; + } + } + + buffer_min_max_dict[data_key] = min_max; + } + + if (!buffer_val_dict.size()) + throw InvalidImageException ("Fixel index image " + fixel_data->name () + " has no associated value image files"); + + c_buffer_dir = buffer_dir_dict.begin()->first; + c_buffer_val = buffer_val_dict.begin()->first; + + buffer_dirty = true; + update_image_buffer (); + } + + + void FixelFolder::update_image_buffer () { + + if (buffer_dirty) { + buffer_dir = (&buffer_dir_dict[c_buffer_dir]); + buffer_val = (&buffer_val_dict[c_buffer_val]); + + reload_dir_and_value_buffers (); + + buffer_dirty = false; + + std::tie(value_min, value_max) = buffer_min_max_dict[c_buffer_val]; + this->set_windowing (value_min, value_max); + greaterthan = value_max; + lessthan = value_min; + } + } + + + void FixelFolder::set_length_type (FixelLengthType value) { + + if (value != FixelLengthType::Unity) { + + size_t value_index = (size_t)value; + + if (value_index < value_types.size()) { + c_buffer_val = value_types[value_index]; + buffer_dirty = true; + std::tie(value_min, value_max) = buffer_min_max_dict[c_buffer_val]; + this->set_windowing (value_min, value_max); + greaterthan = value_max; + lessthan = value_min; + } + + value = Amplitude; + } + + length_type = value; + } + } } } diff --git a/src/gui/mrview/tool/fixel.h b/src/gui/mrview/tool/fixel.h index b05efd9b12..d3aa0ed8d1 100644 --- a/src/gui/mrview/tool/fixel.h +++ b/src/gui/mrview/tool/fixel.h @@ -24,6 +24,7 @@ #include "algo/loop.h" #include "sparse/image.h" #include "sparse/fixel_metric.h" +#include "fixel_format/helpers.h" #include "gui/mrview/displayable.h" #include "gui/mrview/tool/vector.h" @@ -69,6 +70,7 @@ namespace MR } void load_image (); + void reload_dir_and_value_buffers (); void set_line_length_multiplier (float value) { user_line_length_multiplier = value; @@ -86,7 +88,7 @@ namespace MR return line_thickness; } - void set_length_type (FixelLengthType value) { + virtual void set_length_type (FixelLengthType value) { length_type = value; } @@ -102,6 +104,17 @@ namespace MR return colour_type; } + bool virtual internal_buffers_dirty () const { + return false; + } + + void load_scaleby_vector_opts (ComboBoxWithErrorMsg& combo_box) const { + combo_box.clear (); + for (const auto& value_name: value_types) + combo_box.addItem (tr (value_name.c_str ())); + combo_box.setCurrentIndex (0); + } + protected: struct IntPointHasher { size_t operator () (const std::array& v) const { @@ -112,14 +125,17 @@ namespace MR }; virtual void load_image_buffer() = 0; + virtual void update_image_buffer() {} virtual void request_update_interp_image_buffer (const Projection&) = 0; void update_interp_image_buffer (const Projection&, const MR::Header&, const MR::Transform&); std::string filename; MR::Header header; - std::vector buffer_pos; - std::vector buffer_dir; - std::vector buffer_val; + std::vector value_types; + + std::unique_ptr> buffer_pos; + std::vector* buffer_dir; + std::vector* buffer_val; std::vector regular_grid_buffer_pos; std::vector regular_grid_buffer_dir; @@ -133,12 +149,14 @@ namespace MR // To support off-axis rendering, we maintain dict mapping voxels to buffer_pos indices std::unordered_map , std::vector, IntPointHasher> voxel_to_indices_map; + FixelLengthType length_type; private: Vector& fixel_tool; GL::VertexBuffer vertex_buffer; GL::VertexBuffer direction_buffer; - GL::VertexArrayObject vertex_array_object; GL::VertexBuffer value_buffer; + GL::VertexArrayObject vertex_array_object; + GL::VertexArrayObject regular_grid_vao; GL::VertexBuffer regular_grid_vertex_buffer; @@ -148,7 +166,6 @@ namespace MR float voxel_size_length_multipler; float user_line_length_multiplier; float line_thickness; - FixelLengthType length_type; FixelColourType colour_type; }; @@ -172,6 +189,7 @@ namespace MR typedef MR::Sparse::Image FixelSparseImageType; typedef MR::Image FixelPackedImageType; + typedef MR::Image FixelIndexImageType; // Subclassed specialisations of template wrapper // This is because loading of image data is dependent on particular buffer type @@ -182,22 +200,67 @@ namespace MR Fixel (const std::string& filename, Vector& fixel_tool) : FixelType (filename, fixel_tool) { + value_types = {"Unity", "Fixel size", "Associated value"}; + + buffer_pos.reset (new std::vector ()); + buffer_dir = &buffer_dir_store; + buffer_val = &buffer_val_store; fixel_data.reset (new FixelSparseImageType (header)); - load_image(); + load_image (); } void load_image_buffer () override; + + private: + std::vector buffer_dir_store; + std::vector buffer_val_store; }; + class PackedFixel : public FixelType { public: PackedFixel (const std::string& filename, Vector& fixel_tool) : FixelType (filename, fixel_tool) { - fixel_data.reset (new FixelPackedImageType (header.get_image())); - load_image(); + value_types = {"Unity", "Fixel size"}; + + buffer_pos.reset (new std::vector ()); + buffer_dir = &buffer_dir_store; + buffer_val = &buffer_val_store; + fixel_data.reset (new FixelPackedImageType (header.get_image ())); + load_image (); + } + void load_image_buffer () override; + private: + std::vector buffer_dir_store; + std::vector buffer_val_store; + }; + + + class FixelFolder : public FixelType + { + public: + FixelFolder (const std::string& dirname, Vector& fixel_tool) : + FixelType (FixelFormat::find_index_header (dirname).name (), fixel_tool) + { + value_types = {"Unity"}; + + buffer_pos.reset (new std::vector ()); + fixel_data.reset (new FixelIndexImageType (header.get_image ())); + load_image (); } void load_image_buffer () override; + void set_length_type (FixelLengthType value) override; + virtual bool internal_buffers_dirty () const override { return buffer_dirty; } + protected: + void update_image_buffer () override; + private: + bool buffer_dirty; + std::string c_buffer_dir, c_buffer_val; + + std::map> buffer_dir_dict; + std::map> buffer_val_dict; + std::map> buffer_min_max_dict; }; } diff --git a/src/gui/mrview/tool/vector.cpp b/src/gui/mrview/tool/vector.cpp index 491473c395..f30ca6bfb5 100644 --- a/src/gui/mrview/tool/vector.cpp +++ b/src/gui/mrview/tool/vector.cpp @@ -50,7 +50,19 @@ namespace MR if(Path::has_suffix (filenames[i], {".msf", ".msh"})) fixel_image = new Fixel (filenames[i], fixel_tool); else + fixel_image = new FixelFolder (Path::dirname (filenames[i]), fixel_tool); + } + catch (InvalidFixelDirectoryException &) + { + try + { fixel_image = new PackedFixel (filenames[i], fixel_tool); + } + catch (InvalidImageException& e) + { + e.display(); + continue; + } } catch(InvalidImageException& e) { @@ -422,6 +434,8 @@ namespace MR upper_threshold_val += fixel->greaterthan; line_length_multiplier += fixel->get_line_length_multiplier(); line_thickness = fixel->get_line_thickenss(); + + fixel->load_scaleby_vector_opts (*length_combobox); } rate /= indices.size(); @@ -554,24 +568,16 @@ namespace MR void Vector::length_type_slot (int selection) { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - switch (selection) { - case 0: { - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->set_length_type (Unity); - break; - } - case 1: { - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->set_length_type (Amplitude); - break; - } - case 2: { - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->set_length_type (LValue); - break; - } + for (int i = 0; i < indices.size(); ++i) { + const auto fixel = fixel_list_model->get_fixel_image (indices[i]); + fixel->set_length_type ((FixelLengthType)selection); + min_value->setRate (fixel->scaling_rate ()); + max_value->setRate (fixel->scaling_rate ()); + min_value->setValue (fixel->scaling_min ()); + max_value->setValue (fixel->scaling_min ()); } - window().updateGL(); + + window ().updateGL (); } From e1450ecd38e6e910a64d0d64a92a4f9f5e1a4fee Mon Sep 17 00:00:00 2001 From: rtabbara Date: Wed, 13 Jul 2016 17:16:53 +1000 Subject: [PATCH 029/723] Vector tool: Cleaning up multi-selection update of gui controls * Disable scale-by combo-box when we select multiple fixel-files --- src/gui/mrview/tool/vector.cpp | 212 +++++++++++++-------------------- src/gui/mrview/tool/vector.h | 8 +- 2 files changed, 88 insertions(+), 132 deletions(-) diff --git a/src/gui/mrview/tool/vector.cpp b/src/gui/mrview/tool/vector.cpp index f30ca6bfb5..6c1221b132 100644 --- a/src/gui/mrview/tool/vector.cpp +++ b/src/gui/mrview/tool/vector.cpp @@ -74,7 +74,7 @@ namespace MR } beginInsertRows (QModelIndex(), old_size, items.size()); - endInsertRows(); + endInsertRows (); } AbstractFixel* get_fixel_image (QModelIndex& index) { @@ -140,7 +140,6 @@ namespace MR hlayout->setContentsMargins (0, 0, 0, 0); hlayout->setSpacing (0); - //colour_combobox = new QComboBox; colour_combobox = new ComboBoxWithErrorMsg (0, " (variable) "); hlayout->addWidget (new QLabel ("colour by ")); main_box->addLayout (hlayout); @@ -188,7 +187,7 @@ namespace MR hlayout = new HBoxLayout; main_box->addLayout (hlayout); hlayout->addWidget (new QLabel ("scale by ")); - //length_combobox = new QComboBox; + length_combobox = new ComboBoxWithErrorMsg (0, " (variable) "); length_combobox->addItem ("Unity"); length_combobox->addItem ("Fixel size"); @@ -236,12 +235,10 @@ namespace MR main_box->addStretch (); setMinimumSize (main_box->minimumSize()); - update_selection(); + update_gui_controls (); } - Vector::~Vector () {} - void Vector::draw (const Projection& transform, bool is_3D, int, int) { @@ -266,7 +263,6 @@ namespace MR } - size_t Vector::visible_number_colourbars () { size_t total_visible(0); @@ -324,7 +320,7 @@ namespace MR QModelIndex first = fixel_list_model->index (previous_size, 0, QModelIndex()); QModelIndex last = fixel_list_model->index (new_size -1, 0, QModelIndex()); fixel_list_view->selectionModel()->select (QItemSelection (first, last), QItemSelectionModel::Select); - update_selection(); + update_gui_controls (); } } @@ -383,37 +379,22 @@ namespace MR } - void Vector::update_selection () + void Vector::update_gui_controls () { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + size_t n_images (indices.size ()); - colour_combobox->setEnabled (indices.size()); - colourmap_button->setEnabled (indices.size()); - max_value->setEnabled (indices.size()); - min_value->setEnabled (indices.size()); - threshold_lower_box->setEnabled (indices.size()); - threshold_upper_box->setEnabled (indices.size()); - threshold_lower->setEnabled (indices.size()); - threshold_upper->setEnabled (indices.size()); - length_multiplier->setEnabled (indices.size()); - length_combobox->setEnabled (indices.size()); - - if (!indices.size()) { - max_value->setValue (NAN); - min_value->setValue (NAN); - threshold_lower->setValue (NAN); - threshold_upper->setValue (NAN); - length_multiplier->setValue (NAN); + colour_combobox->setEnabled (n_images); + colourmap_button->setEnabled (n_images); + + update_gui_scaling_controls (); + update_gui_threshold_controls (); + + if (!n_images) return; - } - float rate = 0.0f, min_val = 0.0f, max_val = 0.0f; - float lower_threshold_val = 0.0f, upper_threshold_val = 0.0f; - float line_length_multiplier = 0.0f; - float line_thickness(0.f); - int num_lower_threshold = 0, num_upper_threshold = 0; int colourmap_index = -2; - for (int i = 0; i < indices.size(); ++i) { + for (size_t i = 0; i < n_images; ++i) { AbstractFixel* fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[i])); if (colourmap_index != int (fixel->colourmap)) { if (colourmap_index == -2) @@ -421,32 +402,10 @@ namespace MR else colourmap_index = -1; } - rate += fixel->scaling_rate(); - min_val += fixel->scaling_min(); - max_val += fixel->scaling_max(); - num_lower_threshold += (fixel->use_discard_lower() ? 1 : 0); - num_upper_threshold += (fixel->use_discard_upper() ? 1 : 0); - if (!std::isfinite (fixel->lessthan)) - fixel->lessthan = fixel->intensity_min(); - if (!std::isfinite (fixel->greaterthan)) - fixel->greaterthan = fixel->intensity_max(); - lower_threshold_val += fixel->lessthan; - upper_threshold_val += fixel->greaterthan; - line_length_multiplier += fixel->get_line_length_multiplier(); - line_thickness = fixel->get_line_thickenss(); - - fixel->load_scaleby_vector_opts (*length_combobox); } - rate /= indices.size(); - min_val /= indices.size(); - max_val /= indices.size(); - lower_threshold_val /= indices.size(); - upper_threshold_val /= indices.size(); - line_length_multiplier /= indices.size(); - // Not all colourmaps are added to this list; therefore need to find out - // how many menu elements were actually created by ColourMap::create_menu() + // how many menu elements were actually created by ColourMap::create_menu() static size_t colourmap_count = 0; if (!colourmap_count) { for (size_t i = 0; MR::GUI::MRView::ColourMap::maps[i].name; ++i) { @@ -459,84 +418,78 @@ namespace MR for (size_t i = 0; i != colourmap_count; ++i ) colourmap_button->colourmap_actions[i]->setChecked (false); } else { - colourmap_button->colourmap_actions[colourmap_index]->setChecked (true); + colourmap_button->colourmap_actions[colourmap_index]->setChecked (true); } - // FIXME Intensity windowing display values are not correctly updated - min_value->setRate (rate); - max_value->setRate (rate); - min_value->setValue (min_val); - max_value->setValue (max_val); - length_multiplier->setValue (line_length_multiplier); - // Do a better job of setting colour / length with multiple inputs AbstractFixel* first_fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[0])); - const FixelLengthType length_type = first_fixel->get_length_type(); - const FixelColourType colour_type = first_fixel->get_colour_type(); - bool consistent_length = true, consistent_colour = true; - size_t colour_by_value_count = (first_fixel->get_colour_type() == CValue); - for (int i = 1; i < indices.size(); ++i) { - AbstractFixel* fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[i])); - if (fixel->get_length_type() != length_type) - consistent_length = false; - if (fixel->get_colour_type() != colour_type) - consistent_colour = false; - if (fixel->get_colour_type() == CValue) - ++colour_by_value_count; - } + const FixelLengthType length_type = first_fixel->get_length_type (); + const FixelColourType colour_type = first_fixel->get_colour_type (); - if (consistent_length) { - length_combobox->setCurrentIndex (length_type); - } else { - length_combobox->setError(); - } + length_combobox->setCurrentIndex (length_type); + colour_combobox->setCurrentIndex (colour_type); + colourmap_option_group->setEnabled (colour_type == CValue); + } - if (consistent_colour) { - colour_combobox->setCurrentIndex (colour_type); - colourmap_option_group->setEnabled (colour_type == CValue); - } else { - colour_combobox->setError(); - // Enable as long as there is at least one colour-by-value - colourmap_option_group->setEnabled (colour_by_value_count); - } - threshold_lower->setValue (lower_threshold_val); - if (num_lower_threshold) { - if (num_lower_threshold == indices.size()) { - threshold_lower_box->setTristate (false); - threshold_lower_box->setCheckState (Qt::Checked); - threshold_lower->setEnabled (true); - } else { - threshold_lower_box->setTristate (true); - threshold_lower_box->setCheckState (Qt::PartiallyChecked); - threshold_lower->setEnabled (true); - } - } else { - threshold_lower_box->setTristate (false); - threshold_lower_box->setCheckState (Qt::Unchecked); - threshold_lower->setEnabled (false); + void Vector::update_gui_scaling_controls () + { + QModelIndexList indices = fixel_list_view->selectionModel ()->selectedIndexes (); + size_t n_images (indices.size ()); + + max_value->setEnabled (n_images); + min_value->setEnabled (n_images); + length_multiplier->setEnabled (n_images); + length_combobox->setEnabled (n_images == 1); + + if (!n_images) { + max_value->setValue (NAN); + min_value->setValue (NAN); + length_multiplier->setValue (NAN); + return; } - threshold_lower->setRate (rate); - - threshold_upper->setValue (upper_threshold_val); - if (num_upper_threshold) { - if (num_upper_threshold == indices.size()) { - threshold_upper_box->setTristate (false); - threshold_upper_box->setCheckState (Qt::Checked); - threshold_upper->setEnabled (true); - } else { - threshold_upper_box->setTristate (true); - threshold_upper_box->setCheckState (Qt::PartiallyChecked); - threshold_upper->setEnabled (true); - } - } else { - threshold_upper_box->setTristate (false); - threshold_upper_box->setCheckState (Qt::Unchecked); - threshold_upper->setEnabled (false); + + AbstractFixel* first_fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[0])); + + if (n_images == 1) + first_fixel->load_scaleby_vector_opts (*length_combobox); + + min_value->setRate (first_fixel->scaling_rate ()); + max_value->setRate (first_fixel->scaling_rate ()); + min_value->setValue (first_fixel->scaling_min ()); + max_value->setValue (first_fixel->scaling_max ()); + length_multiplier->setValue (first_fixel->get_line_length_multiplier ()); + line_thickness_slider->setValue (static_cast(first_fixel->get_line_thickenss () * 1.0e5f)); + } + + + void Vector::update_gui_threshold_controls () + { + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + size_t n_images (indices.size ()); + + if (!n_images) { + threshold_lower->setValue (NAN); + threshold_upper->setValue (NAN); + return; } - threshold_upper->setRate (rate); - line_thickness_slider->setValue(static_cast(line_thickness * 1.0e5f)); + AbstractFixel* first_fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[0])); + + if (!std::isfinite (first_fixel->lessthan)) + first_fixel->lessthan = first_fixel->intensity_min (); + if (!std::isfinite (first_fixel->greaterthan)) + first_fixel->greaterthan = first_fixel->intensity_max (); + + threshold_lower->setValue (first_fixel->lessthan); + threshold_lower->setRate (first_fixel->scaling_rate ()); + threshold_lower->setEnabled (first_fixel->use_discard_lower ()); + threshold_lower_box->setChecked (first_fixel->use_discard_lower ()); + + threshold_upper->setValue (first_fixel->greaterthan); + threshold_upper->setRate (first_fixel->scaling_rate ()); + threshold_upper->setEnabled (first_fixel->use_discard_lower ()); + threshold_upper_box->setChecked (first_fixel->use_discard_lower ()); } @@ -568,13 +521,12 @@ namespace MR void Vector::length_type_slot (int selection) { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + for (int i = 0; i < indices.size(); ++i) { const auto fixel = fixel_list_model->get_fixel_image (indices[i]); fixel->set_length_type ((FixelLengthType)selection); - min_value->setRate (fixel->scaling_rate ()); - max_value->setRate (fixel->scaling_rate ()); - min_value->setValue (fixel->scaling_min ()); - max_value->setValue (fixel->scaling_min ()); + update_gui_scaling_controls (); + break; } window ().updateGL (); @@ -583,7 +535,7 @@ namespace MR void Vector::selection_changed_slot (const QItemSelection &, const QItemSelection &) { - update_selection (); + update_gui_controls (); } @@ -639,7 +591,7 @@ namespace MR QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); for (int i = 0; i < indices.size(); ++i) fixel_list_model->get_fixel_image (indices[i])->reset_windowing (); - update_selection (); + update_gui_controls (); window().updateGL(); } diff --git a/src/gui/mrview/tool/vector.h b/src/gui/mrview/tool/vector.h index 7cbaddd3da..c098486dcc 100644 --- a/src/gui/mrview/tool/vector.h +++ b/src/gui/mrview/tool/vector.h @@ -39,7 +39,7 @@ namespace MR Vector (Dock* parent); - virtual ~Vector (); + virtual ~Vector () {} void draw (const Projection& transform, bool is_3D, int axis, int slice) override; void draw_colourbars () override; @@ -68,7 +68,6 @@ namespace MR void fixel_close_slot (); void toggle_shown_slot (const QModelIndex&, const QModelIndex&); void hide_all_slot (); - void update_selection(); void on_lock_to_grid_slot (bool is_checked); void on_crop_to_slice_slot (bool is_checked); void opacity_slot (int opacity); @@ -104,6 +103,11 @@ namespace MR void add_images (std::vector& list); void dropEvent (QDropEvent* event) override; + + private: + void update_gui_controls (); + void update_gui_scaling_controls (); + void update_gui_threshold_controls (); }; } } From 33f2e8e36e5dce77fd59bebaeed77f0a930b5cb7 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sun, 17 Jul 2016 15:14:47 +1000 Subject: [PATCH 030/723] Stats: Backend changes - Do not pass maximum / minimum statistics from GLM to statistical enhancer; these were only used in one location (the image-based TFCE wrapper), and can be calculated there using an Eigen reduction, so removal of these makes the functor interfaces much cleaner. - Define a base class for statistical enhancement with appropriate virtual function; this means commands that provide options for statistical enhancement algorithm can operate using a base class pointer. --- cmd/fixelcfestats.cpp | 4 +- cmd/mrclusterstats.cpp | 69 +++++++++++++--------------------- lib/math/stats/glm.cpp | 10 +---- lib/math/stats/glm.h | 2 +- src/stats/cfe.cpp | 2 +- src/stats/cfe.h | 5 ++- src/stats/cluster.cpp | 2 +- src/stats/cluster.h | 6 ++- src/stats/enhance.h | 44 ++++++++++++++++++++++ src/stats/permtest.h | 84 +++++++++++++++++++----------------------- src/stats/tfce.cpp | 3 +- src/stats/tfce.h | 6 ++- 12 files changed, 129 insertions(+), 108 deletions(-) create mode 100644 src/stats/enhance.h diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 71b01134e0..722416cfec 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -26,6 +26,7 @@ #include "math/stats/permutation.h" #include "math/stats/typedefs.h" #include "stats/cfe.h" +#include "stats/enhance.h" #include "stats/permtest.h" #include "dwi/tractography/file.h" #include "dwi/tractography/scalar_file.h" @@ -368,7 +369,8 @@ void run() { } Math::Stats::GLMTTest glm_ttest (data, design, contrast); - Stats::CFE::Enhancer cfe_integrator (connectivity_matrix, cfe_dh, cfe_e, cfe_h); + std::shared_ptr cfe_integrator; + cfe_integrator.reset (new Stats::CFE::Enhancer (connectivity_matrix, cfe_dh, cfe_e, cfe_h)); vector_type empirical_cfe_statistic; Header output_header (input_header); diff --git a/cmd/mrclusterstats.cpp b/cmd/mrclusterstats.cpp index b768b4226a..0bbe968b90 100644 --- a/cmd/mrclusterstats.cpp +++ b/cmd/mrclusterstats.cpp @@ -24,9 +24,10 @@ #include "math/stats/glm.h" #include "math/stats/permutation.h" #include "math/stats/typedefs.h" -#include "stats/tfce.h" #include "stats/cluster.h" +#include "stats/enhance.h" #include "stats/permtest.h" +#include "stats/tfce.h" using namespace MR; @@ -108,6 +109,7 @@ void run() { 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 int num_perms = get_option_value ("nperms", DEFAULT_PERMUTATIONS); const int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_PERMUTATIONS_NONSTATIONARITY); @@ -168,23 +170,17 @@ void run() { output_header.keyval()["num permutations"] = str(num_perms); output_header.keyval()["26 connectivity"] = str(do_26_connectivity); output_header.keyval()["nonstationary adjustment"] = str(do_nonstationary_adjustment); - if (std::isfinite (cluster_forming_threshold)) { - output_header.keyval()["threshold"] = str(cluster_forming_threshold); - } else { + if (use_tfce) { output_header.keyval()["tfce_dh"] = str(tfce_dh); output_header.keyval()["tfce_e"] = str(tfce_E); output_header.keyval()["tfce_h"] = str(tfce_H); + } else { + output_header.keyval()["threshold"] = str(cluster_forming_threshold); } const std::string prefix (argument[4]); - std::string cluster_name (prefix); - if (std::isfinite (cluster_forming_threshold)) - cluster_name.append ("cluster_sizes.mif"); - else - cluster_name.append ("tfce.mif"); - - auto cluster_image = Image::create (cluster_name, output_header); + auto cluster_image = Image::create (prefix + (use_tfce ? "tfce.mif" : "cluster_sizes.mif"), output_header); auto tvalue_image = Image::create (prefix + "tvalue.mif", output_header); auto fwe_pvalue_image = Image::create (prefix + "fwe_pvalue.mif", output_header); auto uncorrected_pvalue_image = Image::create (prefix + "uncorrected_pvalue.mif", output_header); @@ -197,19 +193,14 @@ void run() { vector_type default_cluster_output (num_vox); std::shared_ptr default_cluster_output_neg; vector_type tvalue_output (num_vox); - vector_type empirical_tfce_statistic; + vector_type empirical_enhanced_statistic; vector_type uncorrected_pvalue (num_vox); std::shared_ptr uncorrected_pvalue_neg; const bool compute_negative_contrast = get_options("negative").size() ? true : false; if (compute_negative_contrast) { - std::string cluster_neg_name (prefix); - if (std::isfinite (cluster_forming_threshold)) - cluster_neg_name.append ("cluster_sizes_neg.mif"); - else - cluster_neg_name.append ("tfce_neg.mif"); - cluster_image_neg = Image::create (cluster_neg_name, output_header); + cluster_image_neg = Image::create (prefix + (use_tfce ? "tfce_neg.mif" : "cluster_sizes_neg.mif"), output_header); perm_distribution_neg.reset (new vector_type (num_perms)); default_cluster_output_neg.reset (new vector_type (num_vox)); fwe_pvalue_image_neg = Image::create (prefix + "fwe_pvalue_neg.mif", output_header); @@ -217,38 +208,30 @@ void run() { uncorrected_pvalue_image_neg = Image::create (prefix + "uncorrected_pvalue_neg.mif", output_header); } + std::shared_ptr enhancer; + if (use_tfce) + enhancer.reset (new Stats::TFCE::Enhancer (connector, tfce_dh, tfce_E, tfce_H)); + else + enhancer.reset (new Stats::Cluster::ClusterSize (connector, cluster_forming_threshold)); + { // Do permutation testing: Math::Stats::GLMTTest glm (data, design, contrast); - // Suprathreshold clustering - if (std::isfinite (cluster_forming_threshold)) { - if (do_nonstationary_adjustment) + if (do_nonstationary_adjustment) { + // TODO Is this limitation still required? + if (!use_tfce) throw Exception ("nonstationary adjustment is not currently implemented for threshold-based cluster analysis"); - Stats::Cluster::ClusterSize cluster_size_test (connector, cluster_forming_threshold); - - Stats::PermTest::precompute_default_permutation (glm, cluster_size_test, empirical_tfce_statistic, - default_cluster_output, default_cluster_output_neg, tvalue_output); - - + Stats::PermTest::precompute_empirical_stat (glm, enhancer, nperms_nonstationary, empirical_enhanced_statistic); + } - Stats::PermTest::run_permutations (glm, cluster_size_test, num_perms, empirical_tfce_statistic, - default_cluster_output, default_cluster_output_neg, - perm_distribution, perm_distribution_neg, - uncorrected_pvalue, uncorrected_pvalue_neg); - // TFCE - } else { - Stats::TFCE::Enhancer tfce_integrator (connector, tfce_dh, tfce_E, tfce_H); - if (do_nonstationary_adjustment) - Stats::PermTest::precompute_empirical_stat (glm, tfce_integrator, nperms_nonstationary, empirical_tfce_statistic); + Stats::PermTest::precompute_default_permutation (glm, enhancer, empirical_enhanced_statistic, + default_cluster_output, default_cluster_output_neg, tvalue_output); - Stats::PermTest::precompute_default_permutation (glm, tfce_integrator, empirical_tfce_statistic, - default_cluster_output, default_cluster_output_neg, tvalue_output); + Stats::PermTest::run_permutations (glm, enhancer, num_perms, empirical_enhanced_statistic, + default_cluster_output, default_cluster_output_neg, + perm_distribution, perm_distribution_neg, + uncorrected_pvalue, uncorrected_pvalue_neg); - Stats::PermTest::run_permutations (glm, tfce_integrator, num_perms, empirical_tfce_statistic, - default_cluster_output, default_cluster_output_neg, - perm_distribution, perm_distribution_neg, - uncorrected_pvalue, uncorrected_pvalue_neg); - } } save_matrix (perm_distribution, prefix + "perm_dist.txt"); diff --git a/lib/math/stats/glm.cpp b/lib/math/stats/glm.cpp index ecd719c84f..4dafba6040 100644 --- a/lib/math/stats/glm.cpp +++ b/lib/math/stats/glm.cpp @@ -108,7 +108,7 @@ namespace MR - void GLMTTest::operator() (const std::vector& perm_labelling, vector_type& stats, value_type& max_stat, value_type& min_stat) const + void GLMTTest::operator() (const std::vector& perm_labelling, vector_type& stats) const { stats = vector_type::Zero (y.rows()); matrix_type tvalues, betas, residuals, SX, pinvSX; @@ -127,14 +127,8 @@ namespace MR GLM::ttest (tvalues, SX, pinvSX, tmp, scaled_contrasts, betas, residuals); for (ssize_t n = 0; n < tvalues.rows(); ++n) { value_type val = tvalues(n,0); - if (std::isfinite (val)) { - if (val > max_stat) - max_stat = val; - if (val < min_stat) - min_stat = val; - } else { + if (!std::isfinite (val)) val = value_type(0); - } stats[i+n] = val; } } diff --git a/lib/math/stats/glm.h b/lib/math/stats/glm.h index 714fb54d34..843aafd778 100644 --- a/lib/math/stats/glm.h +++ b/lib/math/stats/glm.h @@ -116,7 +116,7 @@ namespace MR * @param max_stat the maximum t-statistic * @param min_stat the minimum t-statistic */ - void operator() (const std::vector& perm_labelling, vector_type& stats, value_type& max_stat, value_type& min_stat) const; + void operator() (const std::vector& perm_labelling, vector_type& stats) const; size_t num_subjects () const { return y.cols(); } size_t num_elements () const { return y.rows(); } diff --git a/src/stats/cfe.cpp b/src/stats/cfe.cpp index 66a947dc49..c91ea4770e 100644 --- a/src/stats/cfe.cpp +++ b/src/stats/cfe.cpp @@ -97,7 +97,7 @@ namespace MR - value_type Enhancer::operator() (const value_type max_stat, const vector_type& stats, vector_type& enhanced_stats) const + value_type Enhancer::operator() (const vector_type& stats, vector_type& enhanced_stats) const { enhanced_stats = vector_type::Zero (stats.size()); value_type max_enhanced_stat = 0.0; diff --git a/src/stats/cfe.h b/src/stats/cfe.h index 85ed409ec5..37221a8ae9 100644 --- a/src/stats/cfe.h +++ b/src/stats/cfe.h @@ -21,6 +21,7 @@ #include "math/stats/typedefs.h" #include "dwi/tractography/mapping/mapper.h" +#include "stats/enhance.h" namespace MR { @@ -78,13 +79,13 @@ namespace MR - class Enhancer { + class Enhancer : public Stats::EnhancerBase { public: Enhancer (const std::vector >& connectivity_map, const value_type dh, const value_type E, const value_type H); - value_type operator() (const value_type max_stat, const vector_type& stats, vector_type& enhanced_stats) const; + value_type operator() (const vector_type& stats, vector_type& enhanced_stats) const override; protected: diff --git a/src/stats/cluster.cpp b/src/stats/cluster.cpp index 7d27bde59a..4f4e032be5 100644 --- a/src/stats/cluster.cpp +++ b/src/stats/cluster.cpp @@ -26,7 +26,7 @@ namespace MR - value_type ClusterSize::operator() (const value_type /*unused*/, const vector_type& stats, vector_type& get_cluster_sizes) const + value_type ClusterSize::operator() (const vector_type& stats, vector_type& get_cluster_sizes) const { std::vector clusters; std::vector labels (stats.size(), 0); diff --git a/src/stats/cluster.h b/src/stats/cluster.h index 15d1d78b5e..1df0701fce 100644 --- a/src/stats/cluster.h +++ b/src/stats/cluster.h @@ -19,6 +19,8 @@ #include "filter/connected_components.h" #include "math/stats/typedefs.h" +#include "stats/enhance.h" + namespace MR { namespace Stats @@ -35,13 +37,13 @@ namespace MR /** \addtogroup Statistics @{ */ - class ClusterSize { + class ClusterSize : public Stats::EnhancerBase { public: ClusterSize (const Filter::Connector& connector, value_type cluster_forming_threshold) : connector (connector), cluster_forming_threshold (cluster_forming_threshold) { } - value_type operator() (const value_type /*unused*/, const vector_type& stats, vector_type& get_cluster_sizes) const; + value_type operator() (const vector_type& stats, vector_type& get_cluster_sizes) const override; protected: diff --git a/src/stats/enhance.h b/src/stats/enhance.h new file mode 100644 index 0000000000..3f296ed358 --- /dev/null +++ b/src/stats/enhance.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __stats_enhance_h__ +#define __stats_enhance_h__ + +#include "math/stats/typedefs.h" + +namespace MR +{ + namespace Stats + { + + + + // This class defines the standardised interface by which statistical enhancement + // is performed. + class EnhancerBase + { + public: + + // Return value is the maximal enhanced statistic + virtual Math::Stats::value_type operator() (const Math::Stats::vector_type& /*input_statistics*/, Math::Stats::vector_type& /*enhanced_statistics*/) const = 0; + + }; + + + + } +} + +#endif diff --git a/src/stats/permtest.h b/src/stats/permtest.h index 572880e9f8..45d0dd1192 100644 --- a/src/stats/permtest.h +++ b/src/stats/permtest.h @@ -26,6 +26,7 @@ #include "math/stats/permutation.h" #include "math/stats/typedefs.h" +#include "stats/enhance.h" #include "stats/permstack.h" namespace MR @@ -43,12 +44,12 @@ namespace MR /*! A class to pre-compute the empirical enhanced statistic image for non-stationarity correction */ - template + template class PreProcessor { public: PreProcessor (PermutationStack& permutation_stack, const StatsType& stats_calculator, - const EnhancementType& enhancer, + const std::shared_ptr enhancer, vector_type& global_enhanced_sum, std::vector& global_enhanced_count) : perm_stack (permutation_stack), stats_calculator (stats_calculator), @@ -77,9 +78,8 @@ namespace MR void process_permutation (size_t index) { - value_type max_stat = 0.0, min_stat = 0.0; - stats_calculator (perm_stack.permutation (index), stats, max_stat, min_stat); - enhancer (max_stat, stats, enhanced_stats); + stats_calculator (perm_stack.permutation (index), stats); + (*enhancer) (stats, enhanced_stats); for (ssize_t i = 0; i < enhanced_stats.size(); ++i) { if (enhanced_stats[i] > 0.0) { enhanced_sum[i] += enhanced_stats[i]; @@ -90,7 +90,7 @@ namespace MR PermutationStack& perm_stack; StatsType stats_calculator; - EnhancementType enhancer; + std::shared_ptr enhancer; vector_type& global_enhanced_sum; std::vector& global_enhanced_count; vector_type enhanced_sum; @@ -104,12 +104,12 @@ namespace MR /*! A class to perform the permutation testing */ - template + template class Processor { public: Processor (PermutationStack& permutation_stack, const StatsType& stats_calculator, - const EnhancementType& enhancer, + const std::shared_ptr enhancer, const vector_type& empirical_enhanced_statistics, const vector_type& default_enhanced_statistics, const std::shared_ptr default_enhanced_statistics_neg, @@ -153,9 +153,8 @@ namespace MR void process_permutation (size_t index) { - value_type max_stat = 0.0, min_stat = 0.0; - stats_calculator (perm_stack.permutation (index), statistics, max_stat, min_stat); - perm_dist_pos(index) = enhancer (max_stat, statistics, enhanced_statistics); + stats_calculator (perm_stack.permutation (index), statistics); + perm_dist_pos(index) = (*enhancer) (statistics, enhanced_statistics); if (empirical_enhanced_statistics.size()) { perm_dist_pos(index) = 0.0; @@ -173,10 +172,9 @@ namespace MR // Compute the opposite contrast if (perm_dist_neg) { - for (ssize_t i = 0; i < statistics.size(); ++i) - statistics[i] = -statistics[i]; + statistics = -statistics; - (*perm_dist_neg)(index) = enhancer (-min_stat, statistics, enhanced_statistics); + (*perm_dist_neg)(index) = (*enhancer) (statistics, enhanced_statistics); if (empirical_enhanced_statistics.size()) { (*perm_dist_neg)(index) = 0.0; @@ -197,7 +195,7 @@ namespace MR PermutationStack& perm_stack; StatsType stats_calculator; - EnhancementType enhancer; + std::shared_ptr enhancer; const vector_type& empirical_enhanced_statistics; const vector_type& default_enhanced_statistics; const std::shared_ptr default_enhanced_statistics_neg; @@ -215,8 +213,8 @@ namespace MR // Precompute the empircal test statistic for non-stationarity adjustment - template - void precompute_empirical_stat (const StatsType& stats_calculator, const EnhancementType& enhancer, + template + void precompute_empirical_stat (const StatsType& stats_calculator, const std::shared_ptr enhancer, const size_t num_permutations, vector_type& empirical_statistic) { std::vector global_enhanced_count (empirical_statistic.size(), 0); @@ -224,9 +222,9 @@ namespace MR stats_calculator.num_subjects(), "precomputing empirical statistic for non-stationarity adjustment...", false); { - PreProcessor preprocessor (preprocessor_permutations, stats_calculator, enhancer, - empirical_statistic, global_enhanced_count); - auto preprocessor_threads = Thread::run (Thread::multi (preprocessor), "preprocessor threads"); + PreProcessor preprocessor (preprocessor_permutations, stats_calculator, enhancer, + empirical_statistic, global_enhanced_count); + /*auto preprocessor_threads =*/ Thread::run (Thread::multi (preprocessor), "preprocessor threads"); } for (ssize_t i = 0; i < empirical_statistic.size(); ++i) { if (global_enhanced_count[i] > 0) @@ -237,9 +235,9 @@ namespace MR // Precompute the default statistic image and enhanced statistic. We need to precompute this for calculating the uncorrected p-values. - template + template void precompute_default_permutation (const StatsType& stats_calculator, - const EnhancementType& enhancer, + const std::shared_ptr enhancer, const vector_type& empirical_enhanced_statistic, vector_type& default_enhanced_statistics, std::shared_ptr default_enhanced_statistics_neg, @@ -248,38 +246,32 @@ namespace MR std::vector default_labelling (stats_calculator.num_subjects()); for (size_t i = 0; i < default_labelling.size(); ++i) default_labelling[i] = i; - value_type max_stat = 0.0, min_stat = 0.0; - stats_calculator (default_labelling, default_statistics, max_stat, min_stat); - max_stat = enhancer (max_stat, default_statistics, default_enhanced_statistics); + stats_calculator (default_labelling, default_statistics); + (*enhancer) (default_statistics, default_enhanced_statistics); - if (empirical_enhanced_statistic.size()) { - for (ssize_t i = 0; i < default_statistics.size(); ++i) - default_enhanced_statistics[i] /= empirical_enhanced_statistic[i]; - } + if (empirical_enhanced_statistic.size()) + default_enhanced_statistics /= empirical_enhanced_statistic; // Compute the opposite contrast if (default_enhanced_statistics_neg) { - for (ssize_t i = 0; i < default_statistics.size(); ++i) - default_statistics[i] = -default_statistics[i]; + default_statistics = -default_statistics; - max_stat = enhancer (-min_stat, default_statistics, *default_enhanced_statistics_neg); + (*enhancer) (default_statistics, *default_enhanced_statistics_neg); + + if (empirical_enhanced_statistic.size()) + (*default_enhanced_statistics_neg) /= empirical_enhanced_statistic; - if (empirical_enhanced_statistic.size()) { - for (ssize_t i = 0; i < default_statistics.size(); ++i) - (*default_enhanced_statistics_neg)[i] /= empirical_enhanced_statistic[i]; - } // revert default_statistics to positive contrast for output - for (ssize_t i = 0; i < default_statistics.size(); ++i) - default_statistics[i] = -default_statistics[i]; + default_statistics = -default_statistics; } } - template + template inline void run_permutations (const StatsType& stats_calculator, - const EnhancementType& enhancer, + const std::shared_ptr enhancer, const size_t num_permutations, const vector_type& empirical_enhanced_statistic, const vector_type& default_enhanced_statistics, @@ -299,12 +291,12 @@ namespace MR stats_calculator.num_subjects(), "running " + str(num_permutations) + " permutations..."); - Processor processor (permutations, stats_calculator, enhancer, - empirical_enhanced_statistic, - default_enhanced_statistics, default_enhanced_statistics_neg, - perm_dist_pos, perm_dist_neg, - global_uncorrected_pvalue_count, global_uncorrected_pvalue_count_neg); - auto threads = Thread::run (Thread::multi (processor), "permutation threads"); + Processor processor (permutations, stats_calculator, enhancer, + empirical_enhanced_statistic, + default_enhanced_statistics, default_enhanced_statistics_neg, + perm_dist_pos, perm_dist_neg, + global_uncorrected_pvalue_count, global_uncorrected_pvalue_count_neg); + /*auto threads =*/ Thread::run (Thread::multi (processor), "permutation threads"); } for (size_t i = 0; i < stats_calculator.num_elements(); ++i) { diff --git a/src/stats/tfce.cpp b/src/stats/tfce.cpp index 0dda55e0a3..d4a6583111 100644 --- a/src/stats/tfce.cpp +++ b/src/stats/tfce.cpp @@ -32,9 +32,10 @@ namespace MR - value_type Enhancer::operator() (const value_type max_stat, const vector_type& stats, vector_type& enhanced_stats) const + value_type Enhancer::operator() (const vector_type& stats, vector_type& enhanced_stats) const { enhanced_stats = vector_type::Zero (stats.size()); + const value_type max_stat = stats.maxCoeff(); for (value_type h = this->dh; h < max_stat; h += this->dh) { std::vector clusters; diff --git a/src/stats/tfce.h b/src/stats/tfce.h index 7504ac74d6..19db155255 100644 --- a/src/stats/tfce.h +++ b/src/stats/tfce.h @@ -21,6 +21,8 @@ #include "math/stats/permutation.h" #include "math/stats/typedefs.h" +#include "stats/enhance.h" + namespace MR { namespace Stats @@ -37,11 +39,11 @@ namespace MR /** \addtogroup Statistics @{ */ - class Enhancer { + class Enhancer : public Stats::EnhancerBase { public: Enhancer (const Filter::Connector& connector, const value_type dh, const value_type E, const value_type H); - value_type operator() (const value_type max_stat, const vector_type& stats, vector_type& enhanced_stats) const; + value_type operator() (const vector_type& stats, vector_type& enhanced_stats) const override; protected: const Filter::Connector& connector; From 9ba57474aeb5a168f3ab45e31069eefb16496527 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sun, 17 Jul 2016 20:29:33 +1000 Subject: [PATCH 031/723] Stats: Change permutation stack interface Previously, the permutation stack class was passed to the stats processing classes, with a mutexed counter function giving access to permutation indices. With this change, permutation data is instead sent down a multi-threading queue, separating the generation and access of permutation data from subsequent stages of processing. --- cmd/mrclusterstats.cpp | 2 -- src/stats/permstack.cpp | 19 +++++++---- src/stats/permstack.h | 18 +++++++---- src/stats/permtest.h | 72 ++++++++++++++--------------------------- 4 files changed, 49 insertions(+), 62 deletions(-) diff --git a/cmd/mrclusterstats.cpp b/cmd/mrclusterstats.cpp index 0bbe968b90..087c0c1625 100644 --- a/cmd/mrclusterstats.cpp +++ b/cmd/mrclusterstats.cpp @@ -218,7 +218,6 @@ void run() { Math::Stats::GLMTTest glm (data, design, contrast); if (do_nonstationary_adjustment) { - // TODO Is this limitation still required? if (!use_tfce) throw Exception ("nonstationary adjustment is not currently implemented for threshold-based cluster analysis"); Stats::PermTest::precompute_empirical_stat (glm, enhancer, nperms_nonstationary, empirical_enhanced_statistic); @@ -231,7 +230,6 @@ void run() { default_cluster_output, default_cluster_output_neg, perm_distribution, perm_distribution_neg, uncorrected_pvalue, uncorrected_pvalue_neg); - } save_matrix (perm_distribution, prefix + "perm_dist.txt"); diff --git a/src/stats/permstack.cpp b/src/stats/permstack.cpp index 0cb089282d..251c975a02 100644 --- a/src/stats/permstack.cpp +++ b/src/stats/permstack.cpp @@ -26,21 +26,26 @@ namespace MR PermutationStack::PermutationStack (const size_t num_permutations, const size_t num_samples, const std::string msg, const bool include_default) : num_permutations (num_permutations), - current_permutation (0), + counter (0), progress (msg, num_permutations) { - Math::Stats::Permutation::generate (num_permutations, num_samples, permutations, include_default); + Math::Stats::Permutation::generate (num_permutations, num_samples, data, include_default); } - size_t PermutationStack::next() + bool PermutationStack::operator() (Permutation& out) { - std::lock_guard lock (permutation_mutex); - size_t index = current_permutation++; - if (index < permutations.size()) + if (counter < num_permutations) { + out.index = counter; + out.data = data[counter++]; ++progress; - return index; + return true; + } else { + out.index = num_permutations; + out.data.clear(); + return false; + } } diff --git a/src/stats/permstack.h b/src/stats/permstack.h index 38df608cf0..4a131576bc 100644 --- a/src/stats/permstack.h +++ b/src/stats/permstack.h @@ -31,24 +31,30 @@ namespace MR { + class Permutation + { + public: + size_t index; + std::vector data; + }; + class PermutationStack { public: PermutationStack (const size_t num_permutations, const size_t num_samples, const std::string msg, const bool include_default = true); - size_t next(); + bool operator() (Permutation&); - const std::vector& permutation (size_t index) const { - return permutations[index]; + const std::vector& operator[] (size_t index) const { + return data[index]; } const size_t num_permutations; protected: - size_t current_permutation; + size_t counter; ProgressBar progress; - std::vector< std::vector > permutations; - std::mutex permutation_mutex; + std::vector< std::vector > data; }; diff --git a/src/stats/permtest.h b/src/stats/permtest.h index 45d0dd1192..c2596aab8a 100644 --- a/src/stats/permtest.h +++ b/src/stats/permtest.h @@ -47,12 +47,11 @@ namespace MR template class PreProcessor { public: - PreProcessor (PermutationStack& permutation_stack, - const StatsType& stats_calculator, + PreProcessor (const StatsType& stats_calculator, const std::shared_ptr enhancer, vector_type& global_enhanced_sum, std::vector& global_enhanced_count) : - perm_stack (permutation_stack), stats_calculator (stats_calculator), + stats_calculator (stats_calculator), enhancer (enhancer), global_enhanced_sum (global_enhanced_sum), global_enhanced_count (global_enhanced_count), enhanced_sum (vector_type::Zero (global_enhanced_sum.size())), enhanced_count (global_enhanced_sum.size(), 0.0), stats (global_enhanced_sum.size()), @@ -67,18 +66,9 @@ namespace MR } } - void execute () + bool operator() (const Permutation& permutation) { - size_t index; - while (( index = perm_stack.next() ) < perm_stack.num_permutations) - process_permutation (index); - } - - protected: - - void process_permutation (size_t index) - { - stats_calculator (perm_stack.permutation (index), stats); + stats_calculator (permutation.data, stats); (*enhancer) (stats, enhanced_stats); for (ssize_t i = 0; i < enhanced_stats.size(); ++i) { if (enhanced_stats[i] > 0.0) { @@ -86,9 +76,10 @@ namespace MR enhanced_count[i]++; } } + return true; } - PermutationStack& perm_stack; + protected: StatsType stats_calculator; std::shared_ptr enhancer; vector_type& global_enhanced_sum; @@ -107,8 +98,7 @@ namespace MR template class Processor { public: - Processor (PermutationStack& permutation_stack, - const StatsType& stats_calculator, + Processor (const StatsType& stats_calculator, const std::shared_ptr enhancer, const vector_type& empirical_enhanced_statistics, const vector_type& default_enhanced_statistics, @@ -117,7 +107,7 @@ namespace MR std::shared_ptr perm_dist_neg, std::vector& global_uncorrected_pvalue_counter, std::shared_ptr< std::vector > global_uncorrected_pvalue_counter_neg) : - perm_stack (permutation_stack), stats_calculator (stats_calculator), + stats_calculator (stats_calculator), enhancer (enhancer), empirical_enhanced_statistics (empirical_enhanced_statistics), default_enhanced_statistics (default_enhanced_statistics), default_enhanced_statistics_neg (default_enhanced_statistics_neg), statistics (stats_calculator.num_elements()), enhanced_statistics (stats_calculator.num_elements()), @@ -141,27 +131,17 @@ namespace MR } } - void execute () - { - size_t index; - while (( index = perm_stack.next() ) < perm_stack.num_permutations) - process_permutation (index); - } - - - protected: - void process_permutation (size_t index) + bool operator() (const Permutation& permutation) { - stats_calculator (perm_stack.permutation (index), statistics); - perm_dist_pos(index) = (*enhancer) (statistics, enhanced_statistics); + stats_calculator (permutation.data, statistics); + perm_dist_pos[permutation.index] = (*enhancer) (statistics, enhanced_statistics); if (empirical_enhanced_statistics.size()) { - perm_dist_pos(index) = 0.0; + perm_dist_pos[permutation.index] = 0.0; for (ssize_t i = 0; i < enhanced_statistics.size(); ++i) { enhanced_statistics[i] /= empirical_enhanced_statistics[i]; - if (enhanced_statistics[i] > perm_dist_pos(index)) - perm_dist_pos(index) = enhanced_statistics[i]; + perm_dist_pos[permutation.index] = std::max(perm_dist_pos[permutation.index], enhanced_statistics[i]); } } @@ -174,14 +154,13 @@ namespace MR if (perm_dist_neg) { statistics = -statistics; - (*perm_dist_neg)(index) = (*enhancer) (statistics, enhanced_statistics); + (*perm_dist_neg)[permutation.index] = (*enhancer) (statistics, enhanced_statistics); if (empirical_enhanced_statistics.size()) { - (*perm_dist_neg)(index) = 0.0; + (*perm_dist_neg)[permutation.index] = 0.0; for (ssize_t i = 0; i < enhanced_statistics.size(); ++i) { enhanced_statistics[i] /= empirical_enhanced_statistics[i]; - if (enhanced_statistics[i] > (*perm_dist_neg)(index)) - (*perm_dist_neg)(index) = enhanced_statistics[i]; + (*perm_dist_neg)[permutation.index] = std::max ((*perm_dist_neg)[permutation.index], enhanced_statistics[i]); } } @@ -190,10 +169,10 @@ namespace MR (*uncorrected_pvalue_counter_neg)[i]++; } } + return true; } - - PermutationStack& perm_stack; + protected: StatsType stats_calculator; std::shared_ptr enhancer; const vector_type& empirical_enhanced_statistics; @@ -218,13 +197,12 @@ namespace MR const size_t num_permutations, vector_type& empirical_statistic) { std::vector global_enhanced_count (empirical_statistic.size(), 0); - PermutationStack preprocessor_permutations (num_permutations, - stats_calculator.num_subjects(), - "precomputing empirical statistic for non-stationarity adjustment...", false); + PermutationStack permutations (num_permutations, + stats_calculator.num_subjects(), + "precomputing empirical statistic for non-stationarity adjustment...", false); { - PreProcessor preprocessor (preprocessor_permutations, stats_calculator, enhancer, - empirical_statistic, global_enhanced_count); - /*auto preprocessor_threads =*/ Thread::run (Thread::multi (preprocessor), "preprocessor threads"); + PreProcessor preprocessor (stats_calculator, enhancer, empirical_statistic, global_enhanced_count); + Thread::run_queue (permutations, Permutation(), Thread::multi (preprocessor)); } for (ssize_t i = 0; i < empirical_statistic.size(); ++i) { if (global_enhanced_count[i] > 0) @@ -291,12 +269,12 @@ namespace MR stats_calculator.num_subjects(), "running " + str(num_permutations) + " permutations..."); - Processor processor (permutations, stats_calculator, enhancer, + Processor processor (stats_calculator, enhancer, empirical_enhanced_statistic, default_enhanced_statistics, default_enhanced_statistics_neg, perm_dist_pos, perm_dist_neg, global_uncorrected_pvalue_count, global_uncorrected_pvalue_count_neg); - /*auto threads =*/ Thread::run (Thread::multi (processor), "permutation threads"); + Thread::run_queue (permutations, Permutation(), Thread::multi (processor)); } for (size_t i = 0; i < stats_calculator.num_elements(); ++i) { From 780c88f04506dae1048dc3b573458cf2b43763b4 Mon Sep 17 00:00:00 2001 From: rtabbara Date: Thu, 21 Jul 2016 14:55:39 +1000 Subject: [PATCH 032/723] MRView: Vector tool: Add support for fixel-folder * Can still open/render .msf images and .mif vector images * Colouring/Thresholding can now be done be distinct value types --- src/gui/mrview/tool/list.h | 2 +- src/gui/mrview/tool/vector/fixel.cpp | 554 ++++++++++++++ src/gui/mrview/tool/vector/fixel.h | 320 ++++++++ src/gui/mrview/tool/vector/fixelfolder.cpp | 123 +++ src/gui/mrview/tool/vector/fixelfolder.h | 48 ++ src/gui/mrview/tool/vector/packedfixel.cpp | 93 +++ src/gui/mrview/tool/vector/packedfixel.h | 50 ++ src/gui/mrview/tool/vector/sparsefixel.cpp | 73 ++ src/gui/mrview/tool/vector/sparsefixel.h | 51 ++ src/gui/mrview/tool/vector/vector.cpp | 782 ++++++++++++++++++++ src/gui/mrview/tool/vector/vector.h | 124 ++++ src/gui/mrview/tool/vector/vector_structs.h | 78 ++ 12 files changed, 2297 insertions(+), 1 deletion(-) create mode 100644 src/gui/mrview/tool/vector/fixel.cpp create mode 100644 src/gui/mrview/tool/vector/fixel.h create mode 100644 src/gui/mrview/tool/vector/fixelfolder.cpp create mode 100644 src/gui/mrview/tool/vector/fixelfolder.h create mode 100644 src/gui/mrview/tool/vector/packedfixel.cpp create mode 100644 src/gui/mrview/tool/vector/packedfixel.h create mode 100644 src/gui/mrview/tool/vector/sparsefixel.cpp create mode 100644 src/gui/mrview/tool/vector/sparsefixel.h create mode 100644 src/gui/mrview/tool/vector/vector.cpp create mode 100644 src/gui/mrview/tool/vector/vector.h create mode 100644 src/gui/mrview/tool/vector/vector_structs.h diff --git a/src/gui/mrview/tool/list.h b/src/gui/mrview/tool/list.h index f0a09f9c69..2c3f75d651 100644 --- a/src/gui/mrview/tool/list.h +++ b/src/gui/mrview/tool/list.h @@ -19,7 +19,7 @@ #include "gui/mrview/tool/roi_editor/roi.h" #include "gui/mrview/tool/overlay.h" #include "gui/mrview/tool/odf/odf.h" -#include "gui/mrview/tool/vector.h" +#include "gui/mrview/tool/vector/vector.h" #include "gui/mrview/tool/screen_capture.h" #include "gui/mrview/tool/tractography/tractography.h" #include "gui/mrview/tool/connectome/connectome.h" diff --git a/src/gui/mrview/tool/vector/fixel.cpp b/src/gui/mrview/tool/vector/fixel.cpp new file mode 100644 index 0000000000..a2b64a4b36 --- /dev/null +++ b/src/gui/mrview/tool/vector/fixel.cpp @@ -0,0 +1,554 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "gui/mrview/tool/vector/fixel.h" + + +namespace MR +{ + namespace GUI + { + namespace MRView + { + namespace Tool + { + + + AbstractFixel::AbstractFixel (const std::string& filename, Vector& fixel_tool) : + Displayable (filename), + header (MR::Header::open (filename)), + slice_fixel_indices (3), + slice_fixel_sizes (3), + slice_fixel_counts (3), + colour_type (Direction), + scale_type (Unity), + colour_type_index (0), + scale_type_index (0), + threshold_type_index (0), + fixel_tool (fixel_tool), + voxel_size_length_multipler (1.f), + user_line_length_multiplier (1.f), + line_thickness (0.0015f) + { + set_allowed_features (true, true, false); + colourmap = 1; + alpha = 1.0f; + set_use_transparency (true); + colour[0] = colour[1] = colour[2] = 1; + value_min = std::numeric_limits::infinity(); + value_max = -std::numeric_limits::infinity(); + voxel_size_length_multipler = 0.45 * (header.spacing(0) + header.spacing(1) + header.spacing(2)) / 3; + } + + AbstractFixel::~AbstractFixel() + { + MRView::GrabContext context; + vertex_buffer.clear (); + direction_buffer.clear (); + vertex_array_object.clear (); + value_buffer.clear (); + regular_grid_vao.clear (); + regular_grid_vertex_buffer.clear (); + regular_grid_dir_buffer.clear (); + regular_grid_colour_buffer.clear (); + regular_grid_val_buffer.clear (); + } + + std::string AbstractFixel::Shader::vertex_shader_source (const Displayable&) + { + std::string source = + "layout (location = 0) in vec3 centre;\n" + "layout (location = 1) in vec3 direction;\n" + "layout (location = 2) in float fixel_scale;\n" + "layout (location = 3) in float fixel_colour;\n" + "layout (location = 4) in float fixel_thresh;\n" + "out vec3 v_dir;" + "out float v_scale;" + "out float v_colour;" + "out float v_threshold;" + "void main() { " + " gl_Position = vec4(centre, 1);\n" + " v_dir = direction;\n" + " v_scale = fixel_scale;\n" + " v_colour = fixel_colour;\n" + " v_threshold = fixel_thresh;\n" + "}\n"; + + return source; + } + + + std::string AbstractFixel::Shader::geometry_shader_source (const Displayable& fixel) + { + std::string source = + "layout(points) in;\n" + "layout(triangle_strip, max_vertices = 4) out;\n" + "in vec3 v_dir[];\n" + "in float v_colour[];\n" + "in float v_scale[];\n" + "in float v_threshold[];\n" + "uniform mat4 MVP;\n" + "uniform float length_mult;\n" + "uniform vec3 colourmap_colour;\n" + "uniform float line_thickness;\n"; + + switch (color_type) { + case Direction: break; + case CValue: + source += "uniform float offset, scale;\n"; + break; + } + + if (fixel.use_discard_lower()) + source += "uniform float lower;\n"; + if (fixel.use_discard_upper()) + source += "uniform float upper;\n"; + + source += + "flat out vec3 fColour;\n" + "void main() {\n"; + + if (fixel.use_discard_lower()) + source += " if (v_threshold[0] < lower) return;\n"; + if (fixel.use_discard_upper()) + source += " if (v_threshold[0] > upper) return;\n"; + + switch (scale_type) { + case Unity: + source += " vec4 line_offset = length_mult * vec4 (v_dir[0], 0);\n"; + break; + case Value: + source += " vec4 line_offset = length_mult * v_scale[0] * vec4 (v_dir[0], 0);\n"; + break; + } + + switch (color_type) { + case CValue: + if (!ColourMap::maps[colourmap].special) { + source += " float amplitude = clamp ("; + if (fixel.scale_inverted()) + source += "1.0 -"; + source += " scale * (v_colour[0] - offset), 0.0, 1.0);\n"; + } + source += + std::string (" vec3 color;\n") + + ColourMap::maps[colourmap].glsl_mapping + + " fColour = color;\n"; + break; + case Direction: + source += + " fColour = normalize (abs (v_dir[0]));\n"; + break; + default: + break; + } + + source += + " vec4 start = MVP * (gl_in[0].gl_Position - line_offset);\n" + " vec4 end = MVP * (gl_in[0].gl_Position + line_offset);\n" + " vec4 line = end - start;\n" + " vec4 normal = normalize(vec4(-line.y, line.x, 0.0, 0.0));\n" + " vec4 thick_vec = line_thickness * normal;\n" + " gl_Position = start - thick_vec;\n" + " EmitVertex();\n" + " gl_Position = start + thick_vec;\n" + " EmitVertex();\n" + " gl_Position = end - thick_vec;\n" + " EmitVertex();\n" + " gl_Position = end + thick_vec;\n" + " EmitVertex();\n" + " EndPrimitive();\n" + "}\n"; + + return source; + } + + + std::string AbstractFixel::Shader::fragment_shader_source (const Displayable&) + { + std::string source = + "out vec3 outColour;\n" + "flat in vec3 fColour;\n" + "void main(){\n" + " outColour = fColour;\n" + "}\n"; + return source; + } + + + bool AbstractFixel::Shader::need_update (const Displayable& object) const + { + const AbstractFixel& fixel (dynamic_cast (object)); + if (color_type != fixel.colour_type) + return true; + else if (scale_type != fixel.scale_type) + return true; + return Displayable::Shader::need_update (object); + } + + + void AbstractFixel::Shader::update (const Displayable& object) + { + const AbstractFixel& fixel (dynamic_cast (object)); + do_crop_to_slice = fixel.fixel_tool.do_crop_to_slice; + color_type = fixel.colour_type; + scale_type = fixel.scale_type; + Displayable::Shader::update (object); + } + + + void AbstractFixel::render (const Projection& projection) + { + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + start (fixel_shader); + projection.set (fixel_shader); + + update_image_buffers (); + + FixelValue& fixel_threshold = current_fixel_threshold_state (); + + gl::Uniform1f (gl::GetUniformLocation (fixel_shader, "length_mult"), voxel_size_length_multipler * user_line_length_multiplier); + gl::Uniform1f (gl::GetUniformLocation (fixel_shader, "line_thickness"), line_thickness); + + if (use_discard_lower()) + gl::Uniform1f (gl::GetUniformLocation (fixel_shader, "lower"), fixel_threshold.lessthan); + if (use_discard_upper()) + gl::Uniform1f (gl::GetUniformLocation (fixel_shader, "upper"), fixel_threshold.greaterthan); + + if (ColourMap::maps[colourmap].is_colour) + gl::Uniform3f (gl::GetUniformLocation (fixel_shader, "colourmap_colour"), + colour[0]/255.0f, colour[1]/255.0f, colour[2]/255.0f); + + if (fixel_tool.line_opacity < 1.0) { + gl::Enable (gl::BLEND); + gl::Disable (gl::DEPTH_TEST); + gl::DepthMask (gl::FALSE_); + gl::BlendEquation (gl::FUNC_ADD); + gl::BlendFunc (gl::CONSTANT_ALPHA, gl::ONE); + gl::BlendColor (1.0, 1.0, 1.0, fixel_tool.line_opacity); + } else { + gl::Disable (gl::BLEND); + gl::Enable (gl::DEPTH_TEST); + gl::DepthMask (gl::TRUE_); + } + + if (!fixel_tool.do_crop_to_slice) { + vertex_array_object.bind(); + for (size_t x = 0, N = slice_fixel_indices[0].size(); x < N; ++x) { + if (slice_fixel_counts[0][x]) + gl::MultiDrawArrays (gl::POINTS, &slice_fixel_indices[0][x][0], &slice_fixel_sizes[0][x][0], slice_fixel_counts[0][x]); + } + } else { + request_update_interp_image_buffer (projection); + + if (GLsizei points_count = regular_grid_buffer_pos.size()) + gl::DrawArrays(gl::POINTS, 0, points_count); + } + + if (fixel_tool.line_opacity < 1.0) { + gl::Disable (gl::BLEND); + gl::Enable (gl::DEPTH_TEST); + gl::DepthMask (gl::TRUE_); + } + + stop (fixel_shader); + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + } + + + void AbstractFixel::update_image_buffers () + { + if (dir_buffer_dirty) + reload_directions_buffer (); + + if (value_buffer_dirty) + reload_values_buffer (); + + if (colour_buffer_dirty) + reload_colours_buffer (); + + if (threshold_buffer_dirty) + reload_threshold_buffer (); + + dir_buffer_dirty = false; + value_buffer_dirty = false; + colour_buffer_dirty = false; + threshold_buffer_dirty = false; + } + + + void AbstractFixel::update_interp_image_buffer (const Projection& projection, + const MR::Header &fixel_header, + const MR::Transform &transform) + { + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + // Code below "inspired" by ODF::draw + Eigen::Vector3f p (Window::main->target()); + p += projection.screen_normal() * (projection.screen_normal().dot (Window::main->focus() - p)); + p = transform.scanner2voxel.cast() * p; + + if (fixel_tool.do_lock_to_grid) { + p[0] = (int)std::round (p[0]); + p[1] = (int)std::round (p[1]); + p[2] = (int)std::round (p[2]); + } + + p = transform.voxel2scanner.cast() * p; + + Eigen::Vector3f x_dir = projection.screen_to_model_direction (1.0f, 0.0f, projection.depth_of (p)); + x_dir.normalize(); + x_dir = transform.scanner2image.rotation().cast() * x_dir; + x_dir[0] *= fixel_header.spacing(0); + x_dir[1] *= fixel_header.spacing(1); + x_dir[2] *= fixel_header.spacing(2); + x_dir = transform.image2scanner.rotation().cast() * x_dir; + + Eigen::Vector3f y_dir = projection.screen_to_model_direction (0.0f, 1.0f, projection.depth_of (p)); + y_dir.normalize(); + y_dir = transform.scanner2image.rotation().cast() * y_dir; + y_dir[0] *= fixel_header.spacing(0); + y_dir[1] *= fixel_header.spacing(1); + y_dir[2] *= fixel_header.spacing(2); + y_dir = transform.image2scanner.rotation().cast() * y_dir; + + Eigen::Vector3f x_width = projection.screen_to_model_direction (projection.width()/2.0f, 0.0f, projection.depth_of (p)); + int nx = std::ceil (x_width.norm() / x_dir.norm()); + Eigen::Vector3f y_width = projection.screen_to_model_direction (0.0f, projection.height()/2.0f, projection.depth_of (p)); + int ny = std::ceil (y_width.norm() / y_dir.norm()); + + regular_grid_buffer_pos.clear (); + regular_grid_buffer_dir.clear (); + regular_grid_buffer_val.clear (); + regular_grid_buffer_colour.clear (); + regular_grid_buffer_threshold.clear (); + + const auto& val_buffer = current_fixel_value_state ().buffer_store; + const auto& col_buffer = current_fixel_colour_state ().buffer_store; + const auto& threshold_buffer = current_fixel_threshold_state ().buffer_store; + + for (int y = -ny; y <= ny; ++y) { + for (int x = -nx; x <= nx; ++x) { + Eigen::Vector3f scanner_pos = p + float(x)*x_dir + float(y)*y_dir; + Eigen::Vector3f voxel_pos = transform.scanner2voxel.cast() * scanner_pos; + std::array voxel {{ (int)std::round (voxel_pos[0]), (int)std::round (voxel_pos[1]), (int)std::round (voxel_pos[2]) }}; + + // Find and add point indices that correspond to projected voxel + const auto &voxel_indices = voxel_to_indices_map[voxel]; + + // Load all corresponding fixel data into separate buffer + // We can't reuse original buffer because off-axis rendering means that + // two or more points in our regular grid may correspond to the same nearest voxel + for(const GLsizei index : voxel_indices) { + regular_grid_buffer_pos.push_back (scanner_pos); + regular_grid_buffer_dir.push_back (dir_buffer_store[index]); + if (scale_type == Value) + regular_grid_buffer_val.push_back (val_buffer[index]); + if (colour_type == CValue) + regular_grid_buffer_colour.push_back (col_buffer[index]); + regular_grid_buffer_threshold.push_back (threshold_buffer[index]); + } + } + } + + if(!regular_grid_buffer_pos.size()) + return; + + MRView::GrabContext context; + + regular_grid_vao.bind (); + regular_grid_vertex_buffer.bind (gl::ARRAY_BUFFER); + gl::BufferData (gl::ARRAY_BUFFER, regular_grid_buffer_pos.size () * sizeof(Eigen::Vector3f), + ®ular_grid_buffer_pos[0], gl::DYNAMIC_DRAW); + gl::EnableVertexAttribArray (0); + gl::VertexAttribPointer (0, 3, gl::FLOAT, gl::FALSE_, 0, (void*)0); + + // fixel directions + regular_grid_dir_buffer.bind (gl::ARRAY_BUFFER); + gl::BufferData (gl::ARRAY_BUFFER, regular_grid_buffer_dir.size () * sizeof(Eigen::Vector3f), + ®ular_grid_buffer_dir[0], gl::DYNAMIC_DRAW); + gl::EnableVertexAttribArray (1); + gl::VertexAttribPointer (1, 3, gl::FLOAT, gl::FALSE_, 0, (void*)0); + + // fixel values + if (scale_type == Value) { + regular_grid_val_buffer.bind (gl::ARRAY_BUFFER); + gl::BufferData (gl::ARRAY_BUFFER, regular_grid_buffer_val.size () * sizeof(float), + ®ular_grid_buffer_val[0], gl::DYNAMIC_DRAW); + gl::EnableVertexAttribArray (2); + gl::VertexAttribPointer (2, 1, gl::FLOAT, gl::FALSE_, 0, (void*)0); + } + + // fixel colours + if (colour_type == CValue) { + regular_grid_colour_buffer.bind (gl::ARRAY_BUFFER); + gl::BufferData (gl::ARRAY_BUFFER, regular_grid_buffer_colour.size () * sizeof(float), + ®ular_grid_buffer_colour[0], gl::DYNAMIC_DRAW); + gl::EnableVertexAttribArray (3); + gl::VertexAttribPointer (3, 1, gl::FLOAT, gl::FALSE_, 0, (void*)0); + } + + // fixel threshold + regular_grid_threshold_buffer.bind (gl::ARRAY_BUFFER); + gl::BufferData (gl::ARRAY_BUFFER, regular_grid_buffer_threshold.size () * sizeof(float), + ®ular_grid_buffer_threshold[0], gl::DYNAMIC_DRAW); + gl::EnableVertexAttribArray (4); + gl::VertexAttribPointer (4, 1, gl::FLOAT, gl::FALSE_, 0, (void*)0); + + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + } + + + void AbstractFixel::load_image (const std::string& filename) + { + // Make sure to set graphics context! + // We're setting up vertex array objects + MRView::GrabContext context; + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + + load_image_buffer (); + + for (auto& fixel_val : fixel_values) + fixel_val.second.initialise_windowing (); + + set_scale_type_index (0); + set_threshold_type_index (0); + + size_t colour_index(0); + const auto initial_col_key = std::find (colour_types.begin (), colour_types.end (), Path::basename (filename)); + if (initial_col_key != colour_types.end ()) + colour_index = std::distance (colour_types.begin (), initial_col_key); + + set_colour_type_index (colour_index); + + regular_grid_buffer_pos = std::vector (pos_buffer_store.size ()); + + regular_grid_vao.gen (); + + regular_grid_vertex_buffer.gen (); + regular_grid_dir_buffer.gen (); + regular_grid_val_buffer.gen (); + regular_grid_colour_buffer.gen (); + regular_grid_threshold_buffer.gen (); + + vertex_array_object.gen (); + vertex_array_object.bind (); + + vertex_buffer.gen (); + direction_buffer.gen (); + value_buffer.gen (); + colour_buffer.gen (); + threshold_buffer.gen (); + + // voxel centres + vertex_buffer.bind (gl::ARRAY_BUFFER); + gl::BufferData (gl::ARRAY_BUFFER, pos_buffer_store.size () * sizeof(Eigen::Vector3f), &(pos_buffer_store[0][0]), gl::STATIC_DRAW); + gl::EnableVertexAttribArray (0); + gl::VertexAttribPointer (0, 3, gl::FLOAT, gl::FALSE_, 0, (void*)0); + + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + + dir_buffer_dirty = true; + value_buffer_dirty = true; + colour_buffer_dirty = true; + threshold_buffer_dirty = true; + } + + + void AbstractFixel::reload_directions_buffer () + { + MRView::GrabContext context; + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + + vertex_array_object.bind (); + + // fixel directions + direction_buffer.bind (gl::ARRAY_BUFFER); + gl::BufferData (gl::ARRAY_BUFFER, dir_buffer_store.size () * sizeof(Eigen::Vector3f), &(dir_buffer_store)[0][0], gl::STATIC_DRAW); + gl::EnableVertexAttribArray (1); + gl::VertexAttribPointer (1, 3, gl::FLOAT, gl::FALSE_, 0, (void*)0); + + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + } + + + void AbstractFixel::reload_values_buffer () + { + MRView::GrabContext context; + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + + if (scale_type == Unity) + return; + + const auto& fixel_val = current_fixel_value_state (); + const auto& val_buffer = fixel_val.buffer_store; + + vertex_array_object.bind (); + + // fixel values + value_buffer.bind (gl::ARRAY_BUFFER); + gl::BufferData (gl::ARRAY_BUFFER, val_buffer.size () * sizeof(float), &(val_buffer)[0], gl::STATIC_DRAW); + gl::EnableVertexAttribArray (2); + gl::VertexAttribPointer (2, 1, gl::FLOAT, gl::FALSE_, 0, (void*)0); + + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + } + + + void AbstractFixel::reload_colours_buffer () + { + MRView::GrabContext context; + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + + if (colour_type == Direction) + return; + + const auto& fixel_val = current_fixel_colour_state (); + const auto& val_buffer = fixel_val.buffer_store; + + vertex_array_object.bind (); + + // fixel colours + colour_buffer.bind (gl::ARRAY_BUFFER); + gl::BufferData (gl::ARRAY_BUFFER, val_buffer.size () * sizeof(float), &(val_buffer)[0], gl::STATIC_DRAW); + gl::EnableVertexAttribArray (3); + gl::VertexAttribPointer (3, 1, gl::FLOAT, gl::FALSE_, 0, (void*)0); + + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + } + + + void AbstractFixel::reload_threshold_buffer () + { + MRView::GrabContext context; + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + + const auto& fixel_val = current_fixel_threshold_state (); + const auto& val_buffer = fixel_val.buffer_store; + + vertex_array_object.bind (); + + // fixel colours + threshold_buffer.bind (gl::ARRAY_BUFFER); + gl::BufferData (gl::ARRAY_BUFFER, val_buffer.size () * sizeof(float), &(val_buffer)[0], gl::STATIC_DRAW); + gl::EnableVertexAttribArray (4); + gl::VertexAttribPointer (4, 1, gl::FLOAT, gl::FALSE_, 0, (void*)0); + + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + } + + } + } + } +} diff --git a/src/gui/mrview/tool/vector/fixel.h b/src/gui/mrview/tool/vector/fixel.h new file mode 100644 index 0000000000..c72022d9b4 --- /dev/null +++ b/src/gui/mrview/tool/vector/fixel.h @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ +#ifndef __gui_mrview_tool_fixel_fixelimage_h__ +#define __gui_mrview_tool_fixel_fixelimage_h__ + +#include + +#include "header.h" +#include "image.h" +#include "transform.h" + +#include "algo/loop.h" +#include "sparse/image.h" +#include "sparse/fixel_metric.h" +#include "fixel_format/helpers.h" + +#include "gui/mrview/displayable.h" +#include "gui/mrview/tool/vector/vector.h" +#include "gui/mrview/tool/vector/vector_structs.h" + + +namespace MR +{ + namespace GUI + { + namespace MRView + { + namespace Tool + { + class AbstractFixel : public Displayable { + public: + AbstractFixel (const std::string&, Vector&); + ~AbstractFixel(); + + class Shader : public Displayable::Shader { + public: + Shader () : do_crop_to_slice (false), color_type (Direction), scale_type (Value) { } + std::string vertex_shader_source (const Displayable&) override; + std::string geometry_shader_source (const Displayable&) override; + std::string fragment_shader_source (const Displayable&) override; + virtual bool need_update (const Displayable&) const override; + virtual void update (const Displayable&) override; + protected: + bool do_crop_to_slice; + FixelColourType color_type; + FixelScaleType scale_type; + } fixel_shader; + + + void render (const Projection& projection); + + void request_render_colourbar (DisplayableVisitor& visitor) override { + if(colour_type == CValue && show_colour_bar) + visitor.render_fixel_colourbar(*this); + } + + void load_image (const std::string &filename); + + void reload_directions_buffer (); + + void reload_colours_buffer (); + + void reload_values_buffer (); + + void reload_threshold_buffer (); + + void set_line_length_multiplier (float value) { + user_line_length_multiplier = value; + } + + float get_line_length_multiplier () const { + return user_line_length_multiplier; + } + + void set_line_thickness (float value) { + line_thickness = value; + } + + float get_line_thickenss () const { + return line_thickness; + } + + size_t get_scale_type_index () const { + return scale_type_index; + } + + void set_scale_type_index (size_t index) + { + if (index != scale_type_index) { + scale_type_index = index; + scale_type = index == 0 ? Unity : Value; + value_buffer_dirty = true; + } + } + + size_t get_threshold_type_index () const { + return threshold_type_index; + } + + void set_threshold_type_index (size_t index) + { + if (index != threshold_type_index) { + threshold_type_index = index; + if (colour_type == CValue) { + lessthan = get_threshold_lower (); + greaterthan = get_threshold_upper (); + } + + threshold_buffer_dirty = true; + } + } + + size_t get_colour_type_index () const { + return colour_type_index; + } + + void set_colour_type_index (size_t index) { + auto& fixel_val = current_fixel_colour_state (); + fixel_val.current_min = std::isfinite (scaling_min ()) ? scaling_min () : fixel_val.current_min; + fixel_val.current_max = std::isfinite (scaling_max ()) ? scaling_max () : fixel_val.current_max; + + if (index != colour_type_index) { + colour_type_index = index; + colour_type = index == 0 ? Direction : CValue; + colour_buffer_dirty = true; + } + + auto& new_fixel_val = current_fixel_colour_state (); + value_min = new_fixel_val.value_min; + value_max = new_fixel_val.value_max; + if (colour_type == CValue) { + lessthan = get_threshold_lower (); + greaterthan = get_threshold_upper (); + } + set_windowing (new_fixel_val.current_min, new_fixel_val.current_max); + } + + FixelColourType get_colour_type () const { + return colour_type; + } + + float get_threshold_lower () const { + FixelValue& fixel_thresh = current_fixel_threshold_state (); + FixelValue& fixel_val = current_fixel_colour_state (); + return fixel_thresh.get_relative_threshold_lower (fixel_val); + } + + float get_unscaled_threshold_lower () const { + return current_fixel_threshold_state ().lessthan; + } + + void set_threshold_lower (float value) { + FixelValue& fixel_threshold = current_fixel_threshold_state (); + fixel_threshold.lessthan = value; + if (colour_type == CValue) + lessthan = get_threshold_lower (); + } + + float get_threshold_upper () const { + FixelValue& fixel_thresh = current_fixel_threshold_state (); + FixelValue& fixel_val = current_fixel_colour_state (); + return fixel_thresh.get_relative_threshold_upper (fixel_val); + } + + float get_unscaled_threshold_upper () const { + return current_fixel_threshold_state ().greaterthan; + } + + void set_threshold_upper (float value) { + FixelValue& fixel_threshold = current_fixel_threshold_state (); + fixel_threshold.greaterthan = value; + if (colour_type == CValue) + greaterthan = get_threshold_upper (); + } + + float get_unscaled_threshold_rate () const { + FixelValue& fixel_threshold = current_fixel_threshold_state (); + return 1e-3 * (fixel_threshold.value_max - fixel_threshold.value_min); + } + + void load_colourby_combobox_options (ComboBoxWithErrorMsg& combo_box) const { + combo_box.clear (); + for (size_t i = 0, N = colour_types.size (); i < N; ++i) + combo_box.addItem (tr (colour_types[i].c_str ())); + combo_box.setCurrentIndex (colour_type_index); + } + + void load_scaleby_combobox_options (ComboBoxWithErrorMsg& combo_box) const { + combo_box.clear (); + for (const auto& value_name: value_types) + combo_box.addItem (tr (value_name.c_str ())); + combo_box.setCurrentIndex (scale_type_index); + } + + void load_threshold_combobox_options (ComboBoxWithErrorMsg& combo_box) const { + combo_box.clear (); + for (size_t i = 1, N = value_types.size (); i < N; ++i) + combo_box.addItem (tr (value_types[i].c_str ())); + combo_box.setCurrentIndex (threshold_type_index); + } + + protected: + struct IntPointHasher { + size_t operator () (const std::array& v) const { + // This hashing function works best if the fixel image dimensions + // are bounded above by 2^10 x 2^10 x 2^10 = 1024 x 1024 x 1024 + return (v[0] + (v[1] << 10) + (v[2] << 20)); + } + }; + + void update_image_buffers (); + virtual void load_image_buffer() = 0; + virtual void request_update_interp_image_buffer (const Projection&) = 0; + void update_interp_image_buffer (const Projection&, const MR::Header&, const MR::Transform&); + + inline FixelValue& current_fixel_value_state () const { + return fixel_values[value_types[scale_type_index]]; + } + + inline FixelValue& current_fixel_threshold_state () const { + return fixel_values[threshold_types[threshold_type_index]]; + } + + inline FixelValue& current_fixel_colour_state () const { + return fixel_values[colour_types[colour_type_index]]; + } + + MR::Header header; + std::vector colour_types; + std::vector value_types; + std::vector threshold_types; + mutable std::map fixel_values; + + std::vector pos_buffer_store; + std::vector dir_buffer_store; + + std::vector regular_grid_buffer_pos; + std::vector regular_grid_buffer_dir; + std::vector regular_grid_buffer_colour; + std::vector regular_grid_buffer_val; + std::vector regular_grid_buffer_threshold; + + std::vector > > slice_fixel_indices; + std::vector > > slice_fixel_sizes; + std::vector > slice_fixel_counts; + + // Flattened buffer used when cropping to slice + // To support off-axis rendering, we maintain dict mapping voxels to buffer_pos indices + std::unordered_map , std::vector, IntPointHasher> voxel_to_indices_map; + + FixelColourType colour_type; + FixelScaleType scale_type; + size_t colour_type_index; + size_t scale_type_index; + size_t threshold_type_index; + + bool colour_buffer_dirty; + bool value_buffer_dirty; + bool threshold_buffer_dirty; + bool dir_buffer_dirty; + private: + Vector& fixel_tool; + GL::VertexBuffer vertex_buffer; + GL::VertexBuffer direction_buffer; + GL::VertexBuffer colour_buffer; + GL::VertexBuffer value_buffer; + GL::VertexBuffer threshold_buffer; + GL::VertexArrayObject vertex_array_object; + + GL::VertexArrayObject regular_grid_vao; + GL::VertexBuffer regular_grid_vertex_buffer; + GL::VertexBuffer regular_grid_dir_buffer; + GL::VertexBuffer regular_grid_colour_buffer; + GL::VertexBuffer regular_grid_val_buffer; + GL::VertexBuffer regular_grid_threshold_buffer; + + float voxel_size_length_multipler; + float user_line_length_multiplier; + float line_thickness; + }; + + + // Wrapper to generically store fixel data + + template class FixelType : public AbstractFixel + { + public: + FixelType (const std::string& filename, Vector& fixel_tool) : + AbstractFixel (filename, fixel_tool), + transform (header) { } + + protected: + std::unique_ptr fixel_data; + MR::Transform transform; + + void request_update_interp_image_buffer (const Projection& projection) override { + update_interp_image_buffer (projection, *fixel_data, transform); + } + }; + + typedef MR::Sparse::Image FixelSparseImageType; + typedef MR::Image FixelPackedImageType; + typedef MR::Image FixelIndexImageType; + } + } + } +} +#endif diff --git a/src/gui/mrview/tool/vector/fixelfolder.cpp b/src/gui/mrview/tool/vector/fixelfolder.cpp new file mode 100644 index 0000000000..4d728a2aac --- /dev/null +++ b/src/gui/mrview/tool/vector/fixelfolder.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "gui/mrview/tool/vector/fixelfolder.h" + +namespace MR +{ + namespace GUI + { + namespace MRView + { + namespace Tool + { + void FixelFolder::load_image_buffer() + { + for (size_t axis = 0; axis < 3; ++axis) { + slice_fixel_indices[axis].resize (fixel_data->size (axis)); + slice_fixel_sizes [axis].resize (fixel_data->size (axis)); + slice_fixel_counts [axis].resize (fixel_data->size (axis), 0); + } + + // Load fixel index image + for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { + + const std::array voxel {{ int(fixel_data->index (0)), int(fixel_data->index (1)), int(fixel_data->index (2)) }}; + Eigen::Vector3f pos { float(voxel[0]), float(voxel[1]), float(voxel[2]) }; + pos = transform.voxel2scanner.cast () * pos; + + fixel_data->index (3) = 0; + const size_t nfixels = fixel_data->value (); + + for (size_t f = 0; f < nfixels; ++f) { + + pos_buffer_store.push_back (pos); + + const GLint point_index = pos_buffer_store.size () - 1; + + for (size_t axis = 0; axis < 3; ++axis) { + slice_fixel_indices[axis][voxel[axis]].push_back (point_index); + slice_fixel_sizes [axis][voxel[axis]].push_back (1); + slice_fixel_counts [axis][voxel[axis]]++; + } + + voxel_to_indices_map[voxel].push_back (point_index); + } + } + + auto data_headers = FixelFormat::find_data_headers (Path::dirname (fixel_data->name ()), *fixel_data); + + // Load fixel data direction images + // Currently only assuming one direction file exists + for (Header& header : data_headers) { + + if (header.size (1) != 3) continue; + + auto data_image = header.get_image ().with_direct_io (); + + data_image.index (1) = 0; + for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { + fixel_data->index (3) = 0; + const size_t nfixels = fixel_data->value (); + fixel_data->index (3) = 1; + const size_t offset = fixel_data->value (); + for (size_t f = 0; f < nfixels; ++f) { + data_image.index (0) = offset + f; + dir_buffer_store.emplace_back (data_image.row (1)); + } + } + + break; + } + + if (!dir_buffer_store.size ()) + throw InvalidImageException ("Fixel index image " + fixel_data->name () + " has no associated directions file"); + + // Load fixel data value images + for (auto& header : data_headers) { + + if (header.size (1) != 1) continue; + + auto data_image = header.get_image (); + const auto data_key = Path::basename (header.name ()); + fixel_values[data_key]; + value_types.push_back (data_key); + colour_types.push_back (data_key); + threshold_types.push_back (data_key); + + data_image.index (1) = 0; + for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { + fixel_data->index (3) = 0; + const size_t nfixels = fixel_data->value (); + fixel_data->index (3) = 1; + const size_t offset = fixel_data->value (); + + for (size_t f = 0; f < nfixels; ++f) { + data_image.index (0) = offset + f; + float value = data_image.value (); + fixel_values[data_key].add_value (value); + } + } + + } + + if (!fixel_values.size ()) + throw InvalidImageException ("Fixel index image " + fixel_data->name () + " has no associated image data files"); + } + + } + } + } +} diff --git a/src/gui/mrview/tool/vector/fixelfolder.h b/src/gui/mrview/tool/vector/fixelfolder.h new file mode 100644 index 0000000000..bf5e3b79b9 --- /dev/null +++ b/src/gui/mrview/tool/vector/fixelfolder.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ +#ifndef __gui_mrview_tool_vector_fixelfolder_h__ +#define __gui_mrview_tool_vector_fixelfolder_h__ + +#include "gui/mrview/tool/vector/fixel.h" + +namespace MR +{ + namespace GUI + { + namespace MRView + { + namespace Tool + { + class FixelFolder : public FixelType + { + public: + FixelFolder (const std::string& filename, Vector& fixel_tool) : + FixelType (FixelFormat::find_index_header (Path::dirname (filename)).name (), fixel_tool) + { + value_types = {"Unity"}; + colour_types = {"Direction"}; + + fixel_data.reset (new FixelIndexImageType (header.get_image ())); + load_image (filename); + } + + void load_image_buffer () override; + }; + } + } + } +} + +#endif diff --git a/src/gui/mrview/tool/vector/packedfixel.cpp b/src/gui/mrview/tool/vector/packedfixel.cpp new file mode 100644 index 0000000000..769ba1c8ff --- /dev/null +++ b/src/gui/mrview/tool/vector/packedfixel.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "gui/mrview/tool/vector/packedfixel.h" + +namespace MR +{ + namespace GUI + { + namespace MRView + { + namespace Tool + { + void PackedFixel::load_image_buffer() + { + size_t ndim = fixel_data->ndim (); + + if (ndim != 4) + throw InvalidImageException ("Vector image " + filename + + " should contain 4 dimensions. Instead " + + str(ndim) + " found."); + + size_t dim4_len = fixel_data->size (3); + + if (dim4_len % 3) + throw InvalidImageException ("Expecting 4th-dimension size of vector image " + + filename + " to be a multiple of 3. Instead " + + str(dim4_len) + " entries found."); + + for (size_t axis = 0; axis < 3; ++axis) { + slice_fixel_indices[axis].resize (fixel_data->size (axis)); + slice_fixel_sizes [axis].resize (fixel_data->size (axis)); + slice_fixel_counts [axis].resize (fixel_data->size (axis), 0); + } + + const size_t n_fixels = dim4_len / 3; + + FixelValue &fixel_val_store = fixel_values["Length"]; + + for (auto l = Loop(*fixel_data) (*fixel_data); l; ++l) { + + const std::array voxel {{ int(fixel_data->index (0)), + int(fixel_data->index (1)), int(fixel_data->index (2)) }}; + + Eigen::Vector3f pos { float(voxel[0]), float(voxel[1]), float(voxel[2]) }; + pos = transform.voxel2scanner.cast () * pos; + + for (size_t f = 0; f < n_fixels; ++f) { + + // Fetch the vector components + Eigen::Vector3f vector; + fixel_data->index (3) = 3*f; + vector[0] = fixel_data->value (); + fixel_data->index (3)++; + vector[1] = fixel_data->value (); + fixel_data->index (3)++; + vector[2] = fixel_data->value (); + + const float length = vector.norm (); + + pos_buffer_store.push_back (pos); + dir_buffer_store.push_back (vector.normalized()); + fixel_val_store.add_value (length); + + GLint point_index = pos_buffer_store.size () - 1; + + for (size_t axis = 0; axis < 3; ++axis) { + slice_fixel_indices[axis][voxel[axis]].push_back (point_index); + slice_fixel_sizes [axis][voxel[axis]].push_back (1); + slice_fixel_counts [axis][voxel[axis]]++; + } + + voxel_to_indices_map[voxel].push_back (point_index); + } + } + } + + } + } + } +} diff --git a/src/gui/mrview/tool/vector/packedfixel.h b/src/gui/mrview/tool/vector/packedfixel.h new file mode 100644 index 0000000000..25d7c92209 --- /dev/null +++ b/src/gui/mrview/tool/vector/packedfixel.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ +#ifndef __gui_mrview_tool_vector_packedfixel_h__ +#define __gui_mrview_tool_vector_packedfixel_h__ + +#include "gui/mrview/tool/vector/fixel.h" + +namespace MR +{ + namespace GUI + { + namespace MRView + { + namespace Tool + { + class PackedFixel : public FixelType + { + public: + PackedFixel (const std::string& filename, Vector& fixel_tool) : + FixelType (filename, fixel_tool) + { + value_types = {"Unity", "Length"}; + colour_types = {"Direction", "Length"}; + threshold_types = {"Length"}; + fixel_values[value_types[1]]; + fixel_data.reset (new FixelPackedImageType (header.get_image ())); + + load_image (filename); + } + + void load_image_buffer () override; + }; + } + } + } +} + +#endif diff --git a/src/gui/mrview/tool/vector/sparsefixel.cpp b/src/gui/mrview/tool/vector/sparsefixel.cpp new file mode 100644 index 0000000000..e5df0d4557 --- /dev/null +++ b/src/gui/mrview/tool/vector/sparsefixel.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "gui/mrview/tool/vector/sparsefixel.h" + +namespace MR +{ + namespace GUI + { + namespace MRView + { + namespace Tool + { + void SparseFixel::load_image_buffer() + { + for (size_t axis = 0; axis < 3; ++axis) { + const size_t axis_size = fixel_data->size (axis); + slice_fixel_indices[axis].resize (axis_size); + slice_fixel_sizes [axis].resize (axis_size); + slice_fixel_counts [axis].resize (axis_size, 0); + } + + FixelValue& fixel_val_store = fixel_values[value_types[1]]; + FixelValue& fixel_secondary_val_store = fixel_values[value_types[2]]; + + for (auto l = Loop (*fixel_data) (*fixel_data); l; ++l) { + + const std::array voxel {{ int(fixel_data->index (0)), + int(fixel_data->index (1)), int(fixel_data->index (2)) }}; + + Eigen::Vector3f pos { float(voxel[0]), float(voxel[1]), float(voxel[2]) }; + pos = transform.voxel2scanner.cast () * pos; + + for (size_t f = 0; f != fixel_data->value ().size (); ++f) { + + auto val = fixel_data->value ()[f].size; + auto secondary_val = fixel_data->value ()[f].value; + + pos_buffer_store.push_back (pos); + dir_buffer_store.push_back (fixel_data->value ()[f].dir); + + fixel_val_store.add_value (val); + fixel_secondary_val_store.add_value (secondary_val); + + GLint point_index = pos_buffer_store.size () - 1; + + for (size_t axis = 0; axis < 3; ++axis) { + slice_fixel_indices[axis][voxel[axis]].push_back (point_index); + slice_fixel_sizes [axis][voxel[axis]].push_back (1); + slice_fixel_counts [axis][voxel[axis]]++; + } + + voxel_to_indices_map[voxel].push_back (point_index); + } + } + } + + } + } + } +} diff --git a/src/gui/mrview/tool/vector/sparsefixel.h b/src/gui/mrview/tool/vector/sparsefixel.h new file mode 100644 index 0000000000..9f012855c6 --- /dev/null +++ b/src/gui/mrview/tool/vector/sparsefixel.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ +#ifndef __gui_mrview_tool_vector_sparsefixel_h__ +#define __gui_mrview_tool_vector_sparsefixel_h__ + +#include "gui/mrview/tool/vector/fixel.h" + +namespace MR +{ + namespace GUI + { + namespace MRView + { + namespace Tool + { + class SparseFixel : public FixelType + { + public: + SparseFixel (const std::string& filename, Vector& fixel_tool) : + FixelType (filename, fixel_tool) + { + value_types = {"Unity", "Fixel size", "Associated value"}; + colour_types = {"Direction", "Fixel size", "Associated value"}; + threshold_types = {"Fixel size", "Associated value"}; + fixel_values[value_types[1]]; + fixel_values[value_types[2]]; + + fixel_data.reset (new FixelSparseImageType (header)); + load_image (filename); + } + + void load_image_buffer () override; + }; + } + } + } +} + +#endif diff --git a/src/gui/mrview/tool/vector/vector.cpp b/src/gui/mrview/tool/vector/vector.cpp new file mode 100644 index 0000000000..f017da1afe --- /dev/null +++ b/src/gui/mrview/tool/vector/vector.cpp @@ -0,0 +1,782 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "gui/mrview/tool/vector/vector.h" + +#include "mrtrix.h" +#include "gui/mrview/window.h" +#include "gui/mrview/tool/vector/fixel.h" +#include "gui/mrview/tool/vector/sparsefixel.h" +#include "gui/mrview/tool/vector/packedfixel.h" +#include "gui/mrview/tool/vector/fixelfolder.h" +#include "gui/dialog/file.h" +#include "gui/mrview/tool/list_model_base.h" +#include "math/rng.h" + +namespace MR +{ + namespace GUI + { + namespace MRView + { + namespace Tool + { + + + class Vector::Model : public ListModelBase + { + + public: + Model (QObject* parent) : + ListModelBase (parent) { } + + void add_items (std::vector& filenames, Vector& fixel_tool) { + + size_t old_size = items.size(); + for (size_t i = 0, N = filenames.size(); i < N; ++i) { + AbstractFixel* fixel_image(nullptr); + + try + { + if(Path::has_suffix (filenames[i], {".msf", ".msh"})) + fixel_image = new SparseFixel (filenames[i], fixel_tool); + else + fixel_image = new FixelFolder (filenames[i], fixel_tool); + } + catch (InvalidFixelDirectoryException &) + { + try + { + fixel_image = new PackedFixel (filenames[i], fixel_tool); + } + catch (InvalidImageException& e) + { + e.display(); + continue; + } + } + catch(InvalidImageException& e) + { + e.display(); + continue; + } + + items.push_back (std::unique_ptr (fixel_image)); + } + + beginInsertRows (QModelIndex(), old_size, items.size()); + endInsertRows (); + } + + AbstractFixel* get_fixel_image (QModelIndex& index) { + return dynamic_cast(items[index.row()].get()); + } + }; + + + + Vector::Vector (Dock* parent) : + Base (parent), + do_lock_to_grid (true), + do_crop_to_slice (true), + not_3D (true), + line_opacity (1.0) { + + VBoxLayout* main_box = new VBoxLayout (this); + HBoxLayout* layout = new HBoxLayout; + layout->setContentsMargins (0, 0, 0, 0); + layout->setSpacing (0); + + QPushButton* button = new QPushButton (this); + button->setToolTip (tr ("Open fixel image")); + button->setIcon (QIcon (":/open.svg")); + connect (button, SIGNAL (clicked()), this, SLOT (fixel_open_slot ())); + layout->addWidget (button, 1); + + button = new QPushButton (this); + button->setToolTip (tr ("Close fixel image")); + button->setIcon (QIcon (":/close.svg")); + connect (button, SIGNAL (clicked()), this, SLOT (fixel_close_slot ())); + layout->addWidget (button, 1); + + hide_all_button = new QPushButton (this); + hide_all_button->setToolTip (tr ("Hide all fixel images")); + hide_all_button->setIcon (QIcon (":/hide.svg")); + hide_all_button->setCheckable (true); + connect (hide_all_button, SIGNAL (clicked()), this, SLOT (hide_all_slot ())); + layout->addWidget (hide_all_button, 1); + + main_box->addLayout (layout, 0); + + fixel_list_view = new QListView (this); + fixel_list_view->setSelectionMode (QAbstractItemView::ExtendedSelection); + fixel_list_view->setDragEnabled (true); + fixel_list_view->viewport()->setAcceptDrops (true); + fixel_list_view->setDropIndicatorShown (true); + + fixel_list_model = new Model (this); + fixel_list_view->setModel (fixel_list_model); + + connect (fixel_list_model, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (toggle_shown_slot (const QModelIndex&, const QModelIndex&))); + + connect (fixel_list_view->selectionModel(), + SIGNAL (selectionChanged (const QItemSelection &, const QItemSelection &)), + SLOT (selection_changed_slot (const QItemSelection &, const QItemSelection &))); + + main_box->addWidget (fixel_list_view, 1); + + + HBoxLayout* hlayout = new HBoxLayout; + hlayout->setContentsMargins (0, 0, 0, 0); + hlayout->setSpacing (0); + main_box->addLayout (hlayout); + + // Colouring + colour_combobox = new ComboBoxWithErrorMsg (0, " (variable) "); + hlayout->addWidget (new QLabel ("colour by ")); + colour_combobox->addItem ("Direction"); + colour_combobox->addItem ("Value"); + hlayout->addWidget (colour_combobox, 0); + connect (colour_combobox, SIGNAL (activated(int)), this, SLOT (colour_changed_slot(int))); + + colourmap_option_group = new QGroupBox ("Colour map and intensity windowing"); + main_box->addWidget (colourmap_option_group); + hlayout = new HBoxLayout; + colourmap_option_group->setLayout (hlayout); + + colourmap_button = new ColourMapButton (this, *this, false); + + hlayout->addWidget (colourmap_button); + + min_value = new AdjustButton (this); + connect (min_value, SIGNAL (valueChanged()), this, SLOT (on_set_scaling_slot())); + hlayout->addWidget (min_value); + + max_value = new AdjustButton (this); + connect (max_value, SIGNAL (valueChanged()), this, SLOT (on_set_scaling_slot())); + hlayout->addWidget (max_value); + + // Thresholding + hlayout = new HBoxLayout; + main_box->addLayout (hlayout); + hlayout->addWidget (new QLabel ("threshold by ")); + + threshold_combobox = new ComboBoxWithErrorMsg (0, " (variable) "); + threshold_combobox->addItem ("Fixel size"); + threshold_combobox->addItem ("Associated value"); + hlayout->addWidget (threshold_combobox, 0); + connect (threshold_combobox, SIGNAL (activated(int)), this, SLOT (threshold_type_slot(int))); + + QGroupBox* threshold_box = new QGroupBox ("Thresholds"); + main_box->addWidget (threshold_box); + hlayout = new HBoxLayout; + threshold_box->setLayout (hlayout); + + threshold_lower_box = new QCheckBox (this); + connect (threshold_lower_box, SIGNAL (stateChanged(int)), this, SLOT (threshold_lower_changed(int))); + hlayout->addWidget (threshold_lower_box); + threshold_lower = new AdjustButton (this, 0.1); + connect (threshold_lower, SIGNAL (valueChanged()), this, SLOT (threshold_lower_value_changed())); + hlayout->addWidget (threshold_lower); + + threshold_upper_box = new QCheckBox (this); + hlayout->addWidget (threshold_upper_box); + threshold_upper = new AdjustButton (this, 0.1); + connect (threshold_upper_box, SIGNAL (stateChanged(int)), this, SLOT (threshold_upper_changed(int))); + connect (threshold_upper, SIGNAL (valueChanged()), this, SLOT (threshold_upper_value_changed())); + hlayout->addWidget (threshold_upper); + + // Scaling + hlayout = new HBoxLayout; + main_box->addLayout (hlayout); + + hlayout->addWidget (new QLabel ("scale by ")); + length_combobox = new ComboBoxWithErrorMsg (0, " (variable) "); + length_combobox->addItem ("Unity"); + length_combobox->addItem ("Fixel size"); + length_combobox->addItem ("Associated value"); + hlayout->addWidget (length_combobox, 0); + connect (length_combobox, SIGNAL (activated(int)), this, SLOT (length_type_slot(int))); + + hlayout = new HBoxLayout; + main_box->addLayout (hlayout); + hlayout->addWidget (new QLabel ("length multiplier")); + length_multiplier = new AdjustButton (this, 0.01); + length_multiplier->setMin (0.1); + length_multiplier->setValue (1.0); + connect (length_multiplier, SIGNAL (valueChanged()), this, SLOT (length_multiplier_slot())); + hlayout->addWidget (length_multiplier); + + GridLayout* default_opt_grid = new GridLayout; + line_thickness_slider = new QSlider (Qt::Horizontal); + line_thickness_slider->setRange (10,1000); + line_thickness_slider->setSliderPosition (200); + connect (line_thickness_slider, SIGNAL (valueChanged (int)), this, SLOT (line_thickness_slot (int))); + default_opt_grid->addWidget (new QLabel ("line thickness"), 0, 0); + default_opt_grid->addWidget (line_thickness_slider, 0, 1); + + opacity_slider = new QSlider (Qt::Horizontal); + opacity_slider->setRange (1, 1000); + opacity_slider->setSliderPosition (int (1000)); + connect (opacity_slider, SIGNAL (valueChanged (int)), this, SLOT (opacity_slot (int))); + default_opt_grid->addWidget (new QLabel ("opacity"), 1, 0); + default_opt_grid->addWidget (opacity_slider, 1, 1); + + lock_to_grid = new QGroupBox (tr("lock to grid")); + lock_to_grid->setCheckable (true); + lock_to_grid->setChecked (true); + connect (lock_to_grid, SIGNAL (clicked (bool)), this, SLOT (on_lock_to_grid_slot (bool))); + default_opt_grid->addWidget (lock_to_grid, 2, 0, 1, 2); + + crop_to_slice = new QGroupBox (tr("crop to slice")); + crop_to_slice->setCheckable (true); + crop_to_slice->setChecked (true); + connect (crop_to_slice, SIGNAL (clicked (bool)), this, SLOT (on_crop_to_slice_slot (bool))); + default_opt_grid->addWidget (crop_to_slice, 3, 0, 1, 2); + + main_box->addLayout (default_opt_grid, 0); + + main_box->addStretch (); + setMinimumSize (main_box->minimumSize()); + update_gui_controls (); + } + + + + void Vector::draw (const Projection& transform, bool is_3D, int, int) + { + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + not_3D = !is_3D; + for (int i = 0; i < fixel_list_model->rowCount(); ++i) { + if (fixel_list_model->items[i]->show && !hide_all_button->isChecked()) + dynamic_cast(fixel_list_model->items[i].get())->render (transform); + } + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + } + + + void Vector::draw_colourbars () + { + if(hide_all_button->isChecked()) return; + + for (size_t i = 0, N = fixel_list_model->rowCount(); i < N; ++i) { + if (fixel_list_model->items[i]->show) + dynamic_cast(fixel_list_model->items[i].get())->request_render_colourbar(*this); + } + } + + + size_t Vector::visible_number_colourbars () { + size_t total_visible(0); + + if(!hide_all_button->isChecked()) { + for (size_t i = 0, N = fixel_list_model->rowCount(); i < N; ++i) { + AbstractFixel* fixel = dynamic_cast(fixel_list_model->items[i].get()); + if (fixel && fixel->show && !ColourMap::maps[fixel->colourmap].special) + total_visible += 1; + } + } + + return total_visible; + } + + + + void Vector::render_fixel_colourbar(const Tool::AbstractFixel& fixel) + { + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + float min_value = fixel.use_discard_lower() ? + fixel.scaling_min_thresholded() : + fixel.scaling_min(); + + float max_value = fixel.use_discard_upper() ? + fixel.scaling_max_thresholded() : + fixel.scaling_max(); + + window().colourbar_renderer.render (fixel.colourmap, fixel.scale_inverted(), + min_value, max_value, + fixel.scaling_min(), fixel.display_range, + Eigen::Array3f { fixel.colour[0] / 255.0f, fixel.colour[1] / 255.0f, fixel.colour[2] / 255.0f }); + ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; + } + + + void Vector::fixel_open_slot () + { + std::vector list = Dialog::File::get_files (this, + "Select fixel images to open", + GUI::Dialog::File::image_filter_string); + add_images (list); + } + + + void Vector::add_images (std::vector &list) + { + if (list.empty()) + return; + size_t previous_size = fixel_list_model->rowCount(); + fixel_list_model->add_items (list, *this); + + // Some of the images may be invalid, so it could be the case that no images were added + size_t new_size = fixel_list_model->rowCount(); + if(previous_size < new_size) { + QModelIndex first = fixel_list_model->index (previous_size, 0, QModelIndex()); + QModelIndex last = fixel_list_model->index (new_size -1, 0, QModelIndex()); + fixel_list_view->selectionModel()->select (QItemSelection (first, last), QItemSelectionModel::Select); + update_gui_controls (); + } + + window().updateGL (); + } + + + void Vector::dropEvent (QDropEvent* event) + { + static constexpr int max_files = 32; + + const QMimeData* mimeData = event->mimeData(); + if (mimeData->hasUrls()) { + std::vector list; + QList urlList = mimeData->urls(); + for (int i = 0; i < urlList.size() && i < max_files; ++i) { + list.push_back (urlList.at (i).path().toUtf8().constData()); + } + try { + add_images (list); + } + catch (Exception& e) { + e.display(); + } + } + } + + + void Vector::fixel_close_slot () + { + QModelIndexList indexes = fixel_list_view->selectionModel()->selectedIndexes(); + while (indexes.size()) { + fixel_list_model->remove_item (indexes.first()); + indexes = fixel_list_view->selectionModel()->selectedIndexes(); + } + window().updateGL(); + } + + + void Vector::toggle_shown_slot (const QModelIndex& index, const QModelIndex& index2) + { + if (index.row() == index2.row()) { + fixel_list_view->setCurrentIndex(index); + } else { + for (size_t i = 0; i < fixel_list_model->items.size(); ++i) { + if (fixel_list_model->items[i]->show) { + fixel_list_view->setCurrentIndex (fixel_list_model->index (i, 0)); + break; + } + } + } + window().updateGL(); + } + + + void Vector::hide_all_slot () + { + window().updateGL(); + } + + + void Vector::update_gui_controls () + { + update_gui_scaling_controls (); + update_gui_threshold_controls (); + update_gui_colour_controls (); + } + + + void Vector::update_gui_colour_controls (bool reload_colour_types) + { + QModelIndexList indices = fixel_list_view->selectionModel ()->selectedIndexes (); + size_t n_images (indices.size ()); + + colour_combobox->setEnabled (n_images == 1); + colourmap_button->setEnabled (n_images); + + max_value->setEnabled (n_images); + min_value->setEnabled (n_images); + + if (!n_images) { + max_value->setValue (NAN); + min_value->setValue (NAN); + length_multiplier->setValue (NAN); + return; + } + + + if (!n_images) + return; + + int colourmap_index = -2; + for (size_t i = 0; i < n_images; ++i) { + AbstractFixel* fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[i])); + if (colourmap_index != int (fixel->colourmap)) { + if (colourmap_index == -2) + colourmap_index = fixel->colourmap; + else + colourmap_index = -1; + } + } + + // Not all colourmaps are added to this list; therefore need to find out + // how many menu elements were actually created by ColourMap::create_menu() + static size_t colourmap_count = 0; + if (!colourmap_count) { + for (size_t i = 0; MR::GUI::MRView::ColourMap::maps[i].name; ++i) { + if (!MR::GUI::MRView::ColourMap::maps[i].special) + ++colourmap_count; + } + } + + if (colourmap_index < 0) { + for (size_t i = 0; i != colourmap_count; ++i ) + colourmap_button->colourmap_actions[i]->setChecked (false); + } else { + colourmap_button->colourmap_actions[colourmap_index]->setChecked (true); + } + + AbstractFixel* first_fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[0])); + + if (n_images == 1 && reload_colour_types) + first_fixel->load_colourby_combobox_options (*colour_combobox); + + const FixelColourType colour_type = first_fixel->get_colour_type (); + + colour_combobox->setCurrentIndex (first_fixel->get_colour_type_index ()); + colourmap_option_group->setEnabled (colour_type == CValue); + + max_value->setEnabled (colour_type == CValue); + min_value->setEnabled (colour_type == CValue); + + if (colour_type == CValue) { + min_value->setRate (first_fixel->scaling_rate ()); + max_value->setRate (first_fixel->scaling_rate ()); + min_value->setValue (first_fixel->scaling_min ()); + max_value->setValue (first_fixel->scaling_max ()); + } + } + + + void Vector::update_gui_scaling_controls (bool reload_scaling_types) + { + QModelIndexList indices = fixel_list_view->selectionModel ()->selectedIndexes (); + size_t n_images (indices.size ()); + + length_multiplier->setEnabled (n_images); + length_combobox->setEnabled (n_images == 1); + + if (!n_images) { + length_multiplier->setValue (NAN); + return; + } + + AbstractFixel* first_fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[0])); + + if (n_images == 1 && reload_scaling_types) + first_fixel->load_scaleby_combobox_options (*length_combobox); + + length_multiplier->setValue (first_fixel->get_line_length_multiplier ()); + line_thickness_slider->setValue (static_cast(first_fixel->get_line_thickenss () * 1.0e5f)); + + length_combobox->setCurrentIndex (first_fixel->get_scale_type_index ()); + } + + + void Vector::update_gui_threshold_controls (bool reload_threshold_types) + { + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + size_t n_images (indices.size ()); + + threshold_lower->setEnabled (n_images); + threshold_upper->setEnabled (n_images); + threshold_upper_box->setEnabled (n_images); + threshold_lower_box->setEnabled (n_images); + + threshold_combobox->setEnabled (n_images == 1); + + if (!n_images) { + threshold_lower->setValue (NAN); + threshold_upper->setValue (NAN); + return; + } + + AbstractFixel* first_fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[0])); + + if (n_images == 1 && reload_threshold_types) + first_fixel->load_threshold_combobox_options (*threshold_combobox); + + if (!std::isfinite (first_fixel->get_unscaled_threshold_lower ())) + first_fixel->lessthan = first_fixel->intensity_min (); + if (!std::isfinite (first_fixel->get_unscaled_threshold_upper ())) + first_fixel->greaterthan = first_fixel->intensity_max (); + + threshold_lower->setValue (first_fixel->get_unscaled_threshold_lower ()); + threshold_lower->setRate (first_fixel->get_unscaled_threshold_rate ()); + threshold_lower->setEnabled (first_fixel->use_discard_lower ()); + threshold_lower_box->setChecked (first_fixel->use_discard_lower ()); + + threshold_upper->setValue (first_fixel->get_unscaled_threshold_upper ()); + threshold_upper->setRate (first_fixel->get_unscaled_threshold_rate ()); + threshold_upper->setEnabled (first_fixel->use_discard_upper ()); + threshold_upper_box->setChecked (first_fixel->use_discard_upper ()); + + threshold_combobox->setCurrentIndex (first_fixel->get_threshold_type_index ()); + } + + + void Vector::opacity_slot (int opacity) + { + line_opacity = Math::pow2 (static_cast(opacity)) / 1.0e6f; + window().updateGL(); + } + + + void Vector::line_thickness_slot (int thickness) + { + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + for (int i = 0; i < indices.size(); ++i) + fixel_list_model->get_fixel_image (indices[i])->set_line_thickness (static_cast(thickness) / 1.0e5f); + window().updateGL(); + } + + + void Vector::length_multiplier_slot () + { + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + for (int i = 0; i < indices.size(); ++i) + fixel_list_model->get_fixel_image (indices[i])->set_line_length_multiplier (length_multiplier->value()); + window().updateGL(); + } + + + void Vector::length_type_slot (int selection) + { + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + + for (int i = 0; i < indices.size(); ++i) { + const auto fixel = fixel_list_model->get_fixel_image (indices[i]); + fixel->set_scale_type_index (selection); + update_gui_scaling_controls (false); + break; + } + + window ().updateGL (); + } + + + void Vector::threshold_type_slot (int selection) + { + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + + for (int i = 0; i < indices.size(); ++i) { + const auto fixel = fixel_list_model->get_fixel_image (indices[i]); + fixel->set_threshold_type_index (selection); + update_gui_threshold_controls (false); + break; + } + + window ().updateGL (); + } + + + void Vector::selection_changed_slot (const QItemSelection &, const QItemSelection &) + { + update_gui_controls (); + } + + + void Vector::on_lock_to_grid_slot(bool is_checked) + { + do_lock_to_grid = is_checked; + window().updateGL(); + } + + + void Vector::on_crop_to_slice_slot (bool is_checked) + { + do_crop_to_slice = is_checked; + lock_to_grid->setEnabled(do_crop_to_slice); + + window().updateGL(); + } + + + void Vector::toggle_show_colour_bar (bool visible, const ColourMapButton&) + { + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + for (int i = 0; i < indices.size(); ++i) + fixel_list_model->get_fixel_image (indices[i])->show_colour_bar = visible; + window().updateGL(); + } + + + void Vector::selected_colourmap (size_t index, const ColourMapButton&) + { + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + for (int i = 0; i < indices.size(); ++i) { + fixel_list_model->get_fixel_image (indices[i])->colourmap = index; + } + window().updateGL(); + } + + void Vector::selected_custom_colour(const QColor& colour, const ColourMapButton&) + { + if (colour.isValid()) { + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + std::array c_colour{{GLubyte(colour.red()), GLubyte(colour.green()), GLubyte(colour.blue())}}; + for (int i = 0; i < indices.size(); ++i) { + fixel_list_model->get_fixel_image (indices[i])->set_colour (c_colour); + } + window().updateGL(); + } + } + + void Vector::reset_colourmap (const ColourMapButton&) + { + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + for (int i = 0; i < indices.size(); ++i) + fixel_list_model->get_fixel_image (indices[i])->reset_windowing (); + update_gui_controls (); + window().updateGL(); + } + + + void Vector::toggle_invert_colourmap (bool inverted, const ColourMapButton&) + { + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + for (int i = 0; i < indices.size(); ++i) + fixel_list_model->get_fixel_image (indices[i])->set_invert_scale (inverted); + window().updateGL(); + } + + + void Vector::colour_changed_slot (int selection) + { + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + + colourmap_option_group->setEnabled (selection == 0); + for (int i = 0; i < indices.size(); ++i) { + fixel_list_model->get_fixel_image (indices[i])->set_colour_type_index (selection); + update_gui_colour_controls (false); + break; + } + + window().updateGL(); + + } + + + void Vector::on_set_scaling_slot () + { + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + for (int i = 0; i < indices.size(); ++i) + fixel_list_model->get_fixel_image (indices[i])->set_windowing (min_value->value(), max_value->value()); + window().updateGL(); + } + + + void Vector::threshold_lower_changed (int) + { + if (threshold_lower_box->checkState() == Qt::PartiallyChecked) return; + threshold_lower->setEnabled (threshold_lower_box->isChecked()); + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + for (int i = 0; i < indices.size(); ++i) + fixel_list_model->get_fixel_image (indices[i])->set_use_discard_lower (threshold_lower_box->isChecked()); + window().updateGL(); + } + + + void Vector::threshold_upper_changed (int) + { + if (threshold_upper_box->checkState() == Qt::PartiallyChecked) return; + threshold_upper->setEnabled (threshold_upper_box->isChecked()); + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + for (int i = 0; i < indices.size(); ++i) + fixel_list_model->get_fixel_image (indices[i])->set_use_discard_upper (threshold_upper_box->isChecked()); + window().updateGL(); + } + + + void Vector::threshold_lower_value_changed () + { + if (threshold_lower_box->checkState() == Qt::PartiallyChecked) return; + if (threshold_lower_box->isChecked()) { + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + for (int i = 0; i < indices.size(); ++i) { + fixel_list_model->get_fixel_image (indices[i])->set_threshold_lower (threshold_lower->value()); + fixel_list_model->get_fixel_image (indices[i])->set_use_discard_lower (threshold_lower_box->isChecked()); + } + window().updateGL(); + } + } + + + void Vector::threshold_upper_value_changed () + { + if (threshold_upper_box->checkState() == Qt::PartiallyChecked) return; + if (threshold_upper_box->isChecked()) { + QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); + for (int i = 0; i < indices.size(); ++i) { + fixel_list_model->get_fixel_image (indices[i])->set_threshold_upper (threshold_upper->value()); + fixel_list_model->get_fixel_image (indices[i])->set_use_discard_upper (threshold_upper_box->isChecked()); + } + window().updateGL(); + } + } + + + void Vector::add_commandline_options (MR::App::OptionList& options) + { + using namespace MR::App; + options + + OptionGroup ("Vector plot tool options") + + + Option ("vector.load", "Load the specified MRtrix sparse image file (.msf) into the fixel tool.") + + Argument ("image").type_image_in(); + } + + bool Vector::process_commandline_option (const MR::App::ParsedOption& opt) + { + if (opt.opt->is ("vector.load")) { + std::vector list (1, std::string(opt[0])); + try { fixel_list_model->add_items (list , *this); } + catch (Exception& E) { E.display(); } + return true; + } + + return false; + } + + + + + + } + } + } +} + + + + + diff --git a/src/gui/mrview/tool/vector/vector.h b/src/gui/mrview/tool/vector/vector.h new file mode 100644 index 0000000000..b0cdcf1983 --- /dev/null +++ b/src/gui/mrview/tool/vector/vector.h @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __gui_mrview_tool_vector_h__ +#define __gui_mrview_tool_vector_h__ + +#include "gui/mrview/tool/base.h" +#include "gui/projection.h" +#include "gui/mrview/adjust_button.h" +#include "gui/mrview/combo_box_error.h" +#include "gui/mrview/colourmap_button.h" + +namespace MR +{ + namespace GUI + { + namespace MRView + { + namespace Tool + { + class Vector : public Base, public ColourMapButtonObserver, public DisplayableVisitor + { + Q_OBJECT + + public: + class Model; + + Vector (Dock* parent); + + virtual ~Vector () {} + + void draw (const Projection& transform, bool is_3D, int, int) override; + void draw_colourbars () override; + size_t visible_number_colourbars () override; + void render_fixel_colourbar(const Tool::AbstractFixel& fixel) override; + + static void add_commandline_options (MR::App::OptionList& options); + virtual bool process_commandline_option (const MR::App::ParsedOption& opt) override; + + void selected_colourmap(size_t index, const ColourMapButton&) override; + void selected_custom_colour(const QColor& colour, const ColourMapButton&) override; + void toggle_show_colour_bar(bool, const ColourMapButton&) override; + void toggle_invert_colourmap(bool, const ColourMapButton&) override; + void reset_colourmap(const ColourMapButton&) override; + + QPushButton* hide_all_button; + bool do_lock_to_grid, do_crop_to_slice; + bool not_3D; + float line_opacity; + Model* fixel_list_model; + QListView* fixel_list_view; + + + private slots: + void fixel_open_slot (); + void fixel_close_slot (); + void toggle_shown_slot (const QModelIndex&, const QModelIndex&); + void hide_all_slot (); + void on_lock_to_grid_slot (bool is_checked); + void on_crop_to_slice_slot (bool is_checked); + void opacity_slot (int opacity); + void line_thickness_slot (int thickness); + void length_multiplier_slot (); + void length_type_slot (int); + void threshold_type_slot (int); + void selection_changed_slot (const QItemSelection &, const QItemSelection &); + void colour_changed_slot (int); + void on_set_scaling_slot (); + void threshold_lower_changed (int unused); + void threshold_upper_changed (int unused); + void threshold_lower_value_changed (); + void threshold_upper_value_changed (); + + protected: + ComboBoxWithErrorMsg *colour_combobox; + + QGroupBox *colourmap_option_group; + QAction *show_colour_bar, *invert_scale; + ColourMapButton *colourmap_button; + + AdjustButton *min_value, *max_value; + AdjustButton *threshold_lower, *threshold_upper; + QCheckBox *threshold_upper_box, *threshold_lower_box; + + ComboBoxWithErrorMsg *length_combobox; + ComboBoxWithErrorMsg *threshold_combobox; + AdjustButton *length_multiplier; + + QSlider *line_thickness_slider; + QSlider *opacity_slider; + + QGroupBox *lock_to_grid, *crop_to_slice; + + void add_images (std::vector& list); + void dropEvent (QDropEvent* event) override; + + private: + void update_gui_controls (); + void update_gui_scaling_controls (bool reload_scaling_types = true); + void update_gui_colour_controls (bool reload_colour_types = true); + void update_gui_threshold_controls (bool reload_threshold_types = true); + }; + } + } + } +} + +#endif + + + + diff --git a/src/gui/mrview/tool/vector/vector_structs.h b/src/gui/mrview/tool/vector/vector_structs.h new file mode 100644 index 0000000000..f3988a981c --- /dev/null +++ b/src/gui/mrview/tool/vector/vector_structs.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __gui_mrview_tool_vector_structs_h__ +#define __gui_mrview_tool_vector_structs_h__ + +namespace MR +{ + namespace GUI + { + namespace MRView + { + namespace Tool + { + enum FixelColourType { Direction, CValue }; + enum FixelScaleType { Unity, Value }; + + struct FixelValue { + float value_min = std::numeric_limits::max (); + float value_max = std::numeric_limits::min (); + float lessthan, greaterthan; + float current_min, current_max; + std::vector buffer_store; + + + void add_value (float value) { + buffer_store.push_back (value); + value_min = std::min (value_min, value); + value_max = std::max (value_max, value); + } + + + void initialise_windowing () { + lessthan = value_min; + greaterthan = value_max; + set_windowing (value_min, value_max); + } + + + void set_windowing (float min, float max) { + current_min = min; + current_max = max; + } + + + float get_relative_threshold_lower (FixelValue& fixel_value) const { + float factor = (lessthan - value_min)/(value_max - value_min); + + return (1 - factor) * fixel_value.value_min + factor * fixel_value.value_max; + } + + + float get_relative_threshold_upper (FixelValue& fixel_value) const { + float factor = (greaterthan - value_min)/(value_max - value_min); + + return (1 - factor) * fixel_value.value_min + factor * fixel_value.value_max; + } + + }; + + } + } + } +} + +#endif From 8ae7d75e2045640f01fa61de9a7e71177dd75523 Mon Sep 17 00:00:00 2001 From: rtabbara Date: Thu, 21 Jul 2016 14:56:29 +1000 Subject: [PATCH 033/723] MRView: Vector tool: Removing old references * Moved everything to Vector folder --- src/gui/mrview/tool/fixel.cpp | 676 ------------------------------ src/gui/mrview/tool/fixel.h | 270 ------------ src/gui/mrview/tool/vector.cpp | 722 --------------------------------- src/gui/mrview/tool/vector.h | 121 ------ 4 files changed, 1789 deletions(-) delete mode 100644 src/gui/mrview/tool/fixel.cpp delete mode 100644 src/gui/mrview/tool/fixel.h delete mode 100644 src/gui/mrview/tool/vector.cpp delete mode 100644 src/gui/mrview/tool/vector.h diff --git a/src/gui/mrview/tool/fixel.cpp b/src/gui/mrview/tool/fixel.cpp deleted file mode 100644 index a572db314f..0000000000 --- a/src/gui/mrview/tool/fixel.cpp +++ /dev/null @@ -1,676 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - -#include "gui/mrview/tool/fixel.h" - - -namespace MR -{ - namespace GUI - { - namespace MRView - { - namespace Tool - { - - - AbstractFixel::AbstractFixel (const std::string& filename, Vector& fixel_tool) : - Displayable (filename), - filename (filename), - header (MR::Header::open (filename)), - slice_fixel_indices (3), - slice_fixel_sizes (3), - slice_fixel_counts (3), - length_type (Unity), - fixel_tool (fixel_tool), - voxel_size_length_multipler (1.f), - user_line_length_multiplier (1.f), - line_thickness (0.0015f), - colour_type (CValue) - { - set_allowed_features (true, true, false); - colourmap = 1; - alpha = 1.0f; - set_use_transparency (true); - colour[0] = colour[1] = colour[2] = 1; - value_min = std::numeric_limits::infinity(); - value_max = -std::numeric_limits::infinity(); - voxel_size_length_multipler = 0.45 * (header.spacing(0) + header.spacing(1) + header.spacing(2)) / 3; - } - - AbstractFixel::~AbstractFixel() - { - MRView::GrabContext context; - vertex_buffer.clear(); - direction_buffer.clear(); - vertex_array_object.clear(); - value_buffer.clear(); - regular_grid_vao.clear(); - regular_grid_vertex_buffer.clear(); - regular_grid_dir_buffer.clear(); - regular_grid_val_buffer.clear(); - } - - std::string AbstractFixel::Shader::vertex_shader_source (const Displayable&) - { - std::string source = - "layout (location = 0) in vec3 centre;\n" - "layout (location = 1) in vec3 direction;\n" - "layout (location = 2) in vec2 fixel_metrics;\n" - "out vec3 v_dir;" - "out vec2 v_fixel_metrics;" - "void main() { " - " gl_Position = vec4(centre, 1);\n" - " v_dir = direction;" - " v_fixel_metrics = fixel_metrics;" - "}\n"; - - return source; - } - - - std::string AbstractFixel::Shader::geometry_shader_source (const Displayable& fixel) - { - std::string source = - "layout(points) in;\n" - "layout(triangle_strip, max_vertices = 4) out;\n" - "in vec3 v_dir[];\n" - "in vec2 v_fixel_metrics[];\n" - "uniform mat4 MVP;\n" - "uniform float length_mult;\n" - "uniform vec3 colourmap_colour;\n" - "uniform float line_thickness;\n"; - - switch (color_type) { - case Direction: break; - case CValue: - source += "uniform float offset, scale;\n"; - break; - } - - if (fixel.use_discard_lower()) - source += "uniform float lower;\n"; - if (fixel.use_discard_upper()) - source += "uniform float upper;\n"; - - source += - "flat out vec3 fColour;\n" - "void main() {\n"; - - if (fixel.use_discard_lower()) - source += " if (v_fixel_metrics[0].y < lower) return;\n"; - if (fixel.use_discard_upper()) - source += " if (v_fixel_metrics[0].y > upper) return;\n"; - - switch (length_type) { - case Unity: - source += " vec4 line_offset = length_mult * vec4 (v_dir[0], 0);\n"; - break; - case Amplitude: - source += " vec4 line_offset = length_mult * v_fixel_metrics[0].x * vec4 (v_dir[0], 0);\n"; - break; - case LValue: - source += " vec4 line_offset = length_mult * v_fixel_metrics[0].y * vec4 (v_dir[0], 0);\n"; - break; - } - - switch (color_type) { - case CValue: - if (!ColourMap::maps[colourmap].special) { - source += " float amplitude = clamp ("; - if (fixel.scale_inverted()) - source += "1.0 -"; - source += " scale * (v_fixel_metrics[0].y - offset), 0.0, 1.0);\n"; - } - source += - std::string (" vec3 color;\n") + - ColourMap::maps[colourmap].glsl_mapping + - " fColour = color;\n"; - break; - case Direction: - source += - " fColour = normalize (abs (v_dir[0]));\n"; - break; - default: - break; - } - - source += - " vec4 start = MVP * (gl_in[0].gl_Position - line_offset);\n" - " vec4 end = MVP * (gl_in[0].gl_Position + line_offset);\n" - " vec4 line = end - start;\n" - " vec4 normal = normalize(vec4(-line.y, line.x, 0.0, 0.0));\n" - " vec4 thick_vec = line_thickness * normal;\n" - " gl_Position = start - thick_vec;\n" - " EmitVertex();\n" - " gl_Position = start + thick_vec;\n" - " EmitVertex();\n" - " gl_Position = end - thick_vec;\n" - " EmitVertex();\n" - " gl_Position = end + thick_vec;\n" - " EmitVertex();\n" - " EndPrimitive();\n" - "}\n"; - - return source; - } - - - std::string AbstractFixel::Shader::fragment_shader_source (const Displayable&/* fixel*/) - { - std::string source = - "out vec3 outColour;\n" - "flat in vec3 fColour;\n" - "void main(){\n" - " outColour = fColour;\n" - "}\n"; - return source; - } - - - bool AbstractFixel::Shader::need_update (const Displayable& object) const - { - const AbstractFixel& fixel (dynamic_cast (object)); - if (color_type != fixel.colour_type) - return true; - else if (length_type != fixel.length_type) - return true; - else if (fixel.internal_buffers_dirty ()) - return true; - return Displayable::Shader::need_update (object); - } - - - void AbstractFixel::Shader::update (const Displayable& object) - { - const AbstractFixel& fixel (dynamic_cast (object)); - do_crop_to_slice = fixel.fixel_tool.do_crop_to_slice; - color_type = fixel.colour_type; - length_type = fixel.length_type; - Displayable::Shader::update (object); - } - - - void AbstractFixel::render (const Projection& projection) - { - ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; - start (fixel_shader); - projection.set (fixel_shader); - - update_image_buffer (); - - gl::Uniform1f (gl::GetUniformLocation (fixel_shader, "length_mult"), voxel_size_length_multipler * user_line_length_multiplier); - gl::Uniform1f (gl::GetUniformLocation (fixel_shader, "line_thickness"), line_thickness); - - if (use_discard_lower()) - gl::Uniform1f (gl::GetUniformLocation (fixel_shader, "lower"), lessthan); - if (use_discard_upper()) - gl::Uniform1f (gl::GetUniformLocation (fixel_shader, "upper"), greaterthan); - - if (ColourMap::maps[colourmap].is_colour) - gl::Uniform3f (gl::GetUniformLocation (fixel_shader, "colourmap_colour"), - colour[0]/255.0f, colour[1]/255.0f, colour[2]/255.0f); - - if (fixel_tool.line_opacity < 1.0) { - gl::Enable (gl::BLEND); - gl::Disable (gl::DEPTH_TEST); - gl::DepthMask (gl::FALSE_); - gl::BlendEquation (gl::FUNC_ADD); - gl::BlendFunc (gl::CONSTANT_ALPHA, gl::ONE); - gl::BlendColor (1.0, 1.0, 1.0, fixel_tool.line_opacity); - } else { - gl::Disable (gl::BLEND); - gl::Enable (gl::DEPTH_TEST); - gl::DepthMask (gl::TRUE_); - } - - if (!fixel_tool.do_crop_to_slice) { - vertex_array_object.bind(); - for (size_t x = 0, N = slice_fixel_indices[0].size(); x < N; ++x) { - if (slice_fixel_counts[0][x]) - gl::MultiDrawArrays (gl::POINTS, &slice_fixel_indices[0][x][0], &slice_fixel_sizes[0][x][0], slice_fixel_counts[0][x]); - } - } else { - request_update_interp_image_buffer (projection); - - if (GLsizei points_count = regular_grid_buffer_pos.size()) - gl::DrawArrays(gl::POINTS, 0, points_count); - } - - if (fixel_tool.line_opacity < 1.0) { - gl::Disable (gl::BLEND); - gl::Enable (gl::DEPTH_TEST); - gl::DepthMask (gl::TRUE_); - } - - stop (fixel_shader); - ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; - } - - - void AbstractFixel::update_interp_image_buffer (const Projection& projection, - const MR::Header &fixel_header, - const MR::Transform &transform) - { - ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; - // Code below "inspired" by ODF::draw - Eigen::Vector3f p (Window::main->target()); - p += projection.screen_normal() * (projection.screen_normal().dot (Window::main->focus() - p)); - p = transform.scanner2voxel.cast() * p; - - if (fixel_tool.do_lock_to_grid) { - p[0] = (int)std::round (p[0]); - p[1] = (int)std::round (p[1]); - p[2] = (int)std::round (p[2]); - } - - p = transform.voxel2scanner.cast() * p; - - Eigen::Vector3f x_dir = projection.screen_to_model_direction (1.0f, 0.0f, projection.depth_of (p)); - x_dir.normalize(); - x_dir = transform.scanner2image.rotation().cast() * x_dir; - x_dir[0] *= fixel_header.spacing(0); - x_dir[1] *= fixel_header.spacing(1); - x_dir[2] *= fixel_header.spacing(2); - x_dir = transform.image2scanner.rotation().cast() * x_dir; - - Eigen::Vector3f y_dir = projection.screen_to_model_direction (0.0f, 1.0f, projection.depth_of (p)); - y_dir.normalize(); - y_dir = transform.scanner2image.rotation().cast() * y_dir; - y_dir[0] *= fixel_header.spacing(0); - y_dir[1] *= fixel_header.spacing(1); - y_dir[2] *= fixel_header.spacing(2); - y_dir = transform.image2scanner.rotation().cast() * y_dir; - - Eigen::Vector3f x_width = projection.screen_to_model_direction (projection.width()/2.0f, 0.0f, projection.depth_of (p)); - int nx = std::ceil (x_width.norm() / x_dir.norm()); - Eigen::Vector3f y_width = projection.screen_to_model_direction (0.0f, projection.height()/2.0f, projection.depth_of (p)); - int ny = std::ceil (y_width.norm() / y_dir.norm()); - - regular_grid_buffer_pos.clear(); - regular_grid_buffer_dir.clear(); - regular_grid_buffer_val.clear(); - - for (int y = -ny; y <= ny; ++y) { - for (int x = -nx; x <= nx; ++x) { - Eigen::Vector3f scanner_pos = p + float(x)*x_dir + float(y)*y_dir; - Eigen::Vector3f voxel_pos = transform.scanner2voxel.cast() * scanner_pos; - std::array voxel {{ (int)std::round (voxel_pos[0]), (int)std::round (voxel_pos[1]), (int)std::round (voxel_pos[2]) }}; - - // Find and add point indices that correspond to projected voxel - const auto &voxel_indices = voxel_to_indices_map[voxel]; - - // Load all corresponding fixel data into separate buffer - // We can't reuse original buffer because off-axis rendering means that - // two or more points in our regular grid may correspond to the same nearest voxel - for(const GLsizei index : voxel_indices) { - regular_grid_buffer_pos.push_back (scanner_pos); - regular_grid_buffer_dir.push_back ((*buffer_dir)[index]); - regular_grid_buffer_val.push_back ((*buffer_val)[2 * index]); - regular_grid_buffer_val.push_back ((*buffer_val)[(2 * index) + 1]); - } - } - } - - if(!regular_grid_buffer_pos.size()) - return; - - MRView::GrabContext context; - - regular_grid_vao.bind(); - regular_grid_vertex_buffer.bind (gl::ARRAY_BUFFER); - gl::BufferData (gl::ARRAY_BUFFER, regular_grid_buffer_pos.size() * sizeof(Eigen::Vector3f), - ®ular_grid_buffer_pos[0], gl::DYNAMIC_DRAW); - gl::EnableVertexAttribArray (0); - gl::VertexAttribPointer (0, 3, gl::FLOAT, gl::FALSE_, 0, (void*)0); - - // fixel directions - regular_grid_dir_buffer.bind (gl::ARRAY_BUFFER); - gl::BufferData (gl::ARRAY_BUFFER, regular_grid_buffer_dir.size() * sizeof(Eigen::Vector3f), - ®ular_grid_buffer_dir[0], gl::DYNAMIC_DRAW); - gl::EnableVertexAttribArray (1); - gl::VertexAttribPointer (1, 3, gl::FLOAT, gl::FALSE_, 0, (void*)0); - - // fixel sizes and values - regular_grid_val_buffer.bind (gl::ARRAY_BUFFER); - gl::BufferData (gl::ARRAY_BUFFER, regular_grid_buffer_val.size() * sizeof(float), - ®ular_grid_buffer_val[0], gl::DYNAMIC_DRAW); - gl::EnableVertexAttribArray (2); - gl::VertexAttribPointer (2, 2, gl::FLOAT, gl::FALSE_, 0, (void*)0); - ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; - } - - - void AbstractFixel::load_image () - { - // Make sure to set graphics context! - // We're setting up vertex array objects - MRView::GrabContext context; - ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; - - load_image_buffer (); - - regular_grid_buffer_pos = std::vector (buffer_pos->size ()); - - regular_grid_vao.gen (); - - regular_grid_vertex_buffer.gen (); - regular_grid_dir_buffer.gen (); - regular_grid_val_buffer.gen (); - - vertex_array_object.gen (); - vertex_array_object.bind (); - - vertex_buffer.gen (); - direction_buffer.gen (); - value_buffer.gen (); - - reload_dir_and_value_buffers (); - - ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; - } - - - void AbstractFixel::reload_dir_and_value_buffers () - { - MRView::GrabContext context; - ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; - - vertex_array_object.bind (); - - // voxel centres - vertex_buffer.bind (gl::ARRAY_BUFFER); - gl::BufferData (gl::ARRAY_BUFFER, buffer_pos->size () * sizeof(Eigen::Vector3f), &(*buffer_pos)[0][0], gl::STATIC_DRAW); - gl::EnableVertexAttribArray (0); - gl::VertexAttribPointer (0, 3, gl::FLOAT, gl::FALSE_, 0, (void*)0); - - // fixel directions - direction_buffer.bind (gl::ARRAY_BUFFER); - gl::BufferData (gl::ARRAY_BUFFER, buffer_dir->size () * sizeof(Eigen::Vector3f), &(*buffer_dir)[0][0], gl::STATIC_DRAW); - gl::EnableVertexAttribArray (1); - gl::VertexAttribPointer (1, 3, gl::FLOAT, gl::FALSE_, 0, (void*)0); - - // fixel sizes and values - value_buffer.bind (gl::ARRAY_BUFFER); - gl::BufferData (gl::ARRAY_BUFFER, buffer_val->size () * sizeof(float), &(*buffer_val)[0], gl::STATIC_DRAW); - gl::EnableVertexAttribArray (2); - gl::VertexAttribPointer (2, 2, gl::FLOAT, gl::FALSE_, 0, (void*)0); - - ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; - } - - - void Fixel::load_image_buffer() - { - for (size_t axis = 0; axis < 3; ++axis) { - const size_t axis_size = fixel_data->size (axis); - slice_fixel_indices[axis].resize (axis_size); - slice_fixel_sizes [axis].resize (axis_size); - slice_fixel_counts [axis].resize (axis_size, 0); - } - - for (auto l = Loop(*fixel_data) (*fixel_data); l; ++l) { - - const std::array voxel {{ int(fixel_data->index(0)), int(fixel_data->index(1)), int(fixel_data->index(2)) }}; - Eigen::Vector3f pos { float(voxel[0]), float(voxel[1]), float(voxel[2]) }; - pos = transform.voxel2scanner.cast() * pos; - - for (size_t f = 0; f != fixel_data->value().size(); ++f) { - - if (fixel_data->value()[f].value > value_max) - value_max = fixel_data->value()[f].value; - if (fixel_data->value()[f].value < value_min) - value_min = fixel_data->value()[f].value; - - buffer_pos->push_back (pos); - buffer_dir->push_back (fixel_data->value()[f].dir); - buffer_val->push_back (fixel_data->value()[f].size); - buffer_val->push_back (fixel_data->value()[f].value); - - GLint point_index = buffer_pos->size() - 1; - - for (size_t axis = 0; axis < 3; ++axis) { - slice_fixel_indices[axis][voxel[axis]].push_back (point_index); - slice_fixel_sizes [axis][voxel[axis]].push_back (1); - slice_fixel_counts [axis][voxel[axis]]++; - } - - voxel_to_indices_map[voxel].push_back (point_index); - } - } - - this->set_windowing (value_min, value_max); - greaterthan = value_max; - lessthan = value_min; - } - - - void PackedFixel::load_image_buffer() - { - size_t ndim = fixel_data->ndim(); - - if (ndim != 4) - throw InvalidImageException ("Vector image " + filename - + " should contain 4 dimensions. Instead " - + str(ndim) + " found."); - - size_t dim4_len = fixel_data->size (3); - - if (dim4_len % 3) - throw InvalidImageException ("Expecting 4th-dimension size of vector image " - + filename + " to be a multiple of 3. Instead " - + str(dim4_len) + " entries found."); - - for (size_t axis = 0; axis < 3; ++axis) { - slice_fixel_indices[axis].resize (fixel_data->size (axis)); - slice_fixel_sizes [axis].resize (fixel_data->size (axis)); - slice_fixel_counts [axis].resize (fixel_data->size (axis), 0); - } - - const size_t n_fixels = dim4_len / 3; - - for (auto l = Loop(*fixel_data) (*fixel_data); l; ++l) { - - const std::array voxel {{ int(fixel_data->index(0)), int(fixel_data->index(1)), int(fixel_data->index(2)) }}; - Eigen::Vector3f pos { float(voxel[0]), float(voxel[1]), float(voxel[2]) }; - pos = transform.voxel2scanner.cast() * pos; - - for (size_t f = 0; f < n_fixels; ++f) { - - // Fetch the vector components - Eigen::Vector3f vector; - fixel_data->index(3) = 3*f; - vector[0] = fixel_data->value(); - fixel_data->index(3)++; - vector[1] = fixel_data->value(); - fixel_data->index(3)++; - vector[2] = fixel_data->value(); - - const float length = vector.norm(); - value_min = std::min (value_min, length); - value_max = std::max (value_max, length); - - buffer_pos->push_back (pos); - buffer_dir->push_back (vector.normalized()); - - // Use the vector length to represent both fixel amplitude and value - buffer_val->push_back (length); - buffer_val->push_back (length); - - GLint point_index = buffer_pos->size() - 1; - - for (size_t axis = 0; axis < 3; ++axis) { - slice_fixel_indices[axis][voxel[axis]].push_back (point_index); - slice_fixel_sizes [axis][voxel[axis]].push_back (1); - slice_fixel_counts [axis][voxel[axis]]++; - } - - voxel_to_indices_map[voxel].push_back (point_index); - } - } - - this->set_windowing (value_min, value_max); - greaterthan = value_max; - lessthan = value_min; - } - - - void FixelFolder::load_image_buffer() - { - for (size_t axis = 0; axis < 3; ++axis) { - slice_fixel_indices[axis].resize (fixel_data->size (axis)); - slice_fixel_sizes [axis].resize (fixel_data->size (axis)); - slice_fixel_counts [axis].resize (fixel_data->size (axis), 0); - } - - // Load fixel index image - for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { - - const std::array voxel {{ int(fixel_data->index(0)), int(fixel_data->index(1)), int(fixel_data->index(2)) }}; - Eigen::Vector3f pos { float(voxel[0]), float(voxel[1]), float(voxel[2]) }; - pos = transform.voxel2scanner.cast() * pos; - - fixel_data->index (3) = 0; - const size_t nfixels = fixel_data->value (); - - for (size_t f = 0; f < nfixels; ++f) { - - buffer_pos->push_back (pos); - - const GLint point_index = buffer_pos->size () - 1; - - for (size_t axis = 0; axis < 3; ++axis) { - slice_fixel_indices[axis][voxel[axis]].push_back (point_index); - slice_fixel_sizes [axis][voxel[axis]].push_back (1); - slice_fixel_counts [axis][voxel[axis]]++; - } - - voxel_to_indices_map[voxel].push_back (point_index); - } - } - - auto data_headers = FixelFormat::find_data_headers (Path::dirname (fixel_data->name ()), *fixel_data); - - // Load fixel data direction images - for (Header& header : data_headers) { - - if (header.size (1) != 3) continue; - - auto data_image = header.get_image ().with_direct_io (); - const auto data_key = Path::basename (data_image.name ()); - buffer_dir_dict[data_key]; - - data_image.index (1) = 0; - for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { - fixel_data->index (3) = 0; - const size_t nfixels = fixel_data->value (); - fixel_data->index (3) = 1; - const size_t offset = fixel_data->value (); - for (size_t f = 0; f < nfixels; ++f) { - data_image.index (0) = offset + f; - buffer_dir_dict[data_key].emplace_back (data_image.row (1)); - } - } - } - - if (!buffer_dir_dict.size()) - throw InvalidImageException ("Fixel index image " + fixel_data->name () + " has no associated directions file"); - - // Load fixel data value images - for (auto& header : data_headers) { - - if (header.size (1) != 1) continue; - - auto data_image = header.get_image (); - const auto data_key = Path::basename (header.name ()); - buffer_val_dict[data_key]; - std::pair min_max = { std::numeric_limits::max (), std::numeric_limits::min () }; - - value_types.push_back (data_key); - - data_image.index (1) = 0; - for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { - fixel_data->index (3) = 0; - const size_t nfixels = fixel_data->value (); - fixel_data->index (3) = 1; - const size_t offset = fixel_data->value (); - - for (size_t f = 0; f < nfixels; ++f) { - data_image.index (0) = offset + f; - float value = data_image.value (); - buffer_val_dict[data_key].emplace_back (value); - // FIXME: Shader needs two values atm - buffer_val_dict[data_key].emplace_back (value); - min_max = { std::min (min_max.first, value), std::max (min_max.second, value) }; - } - } - - buffer_min_max_dict[data_key] = min_max; - } - - if (!buffer_val_dict.size()) - throw InvalidImageException ("Fixel index image " + fixel_data->name () + " has no associated value image files"); - - c_buffer_dir = buffer_dir_dict.begin()->first; - c_buffer_val = buffer_val_dict.begin()->first; - - buffer_dirty = true; - update_image_buffer (); - } - - - void FixelFolder::update_image_buffer () { - - if (buffer_dirty) { - buffer_dir = (&buffer_dir_dict[c_buffer_dir]); - buffer_val = (&buffer_val_dict[c_buffer_val]); - - reload_dir_and_value_buffers (); - - buffer_dirty = false; - - std::tie(value_min, value_max) = buffer_min_max_dict[c_buffer_val]; - this->set_windowing (value_min, value_max); - greaterthan = value_max; - lessthan = value_min; - } - } - - - void FixelFolder::set_length_type (FixelLengthType value) { - - if (value != FixelLengthType::Unity) { - - size_t value_index = (size_t)value; - - if (value_index < value_types.size()) { - c_buffer_val = value_types[value_index]; - buffer_dirty = true; - std::tie(value_min, value_max) = buffer_min_max_dict[c_buffer_val]; - this->set_windowing (value_min, value_max); - greaterthan = value_max; - lessthan = value_min; - } - - value = Amplitude; - } - - length_type = value; - } - - } - } - } -} diff --git a/src/gui/mrview/tool/fixel.h b/src/gui/mrview/tool/fixel.h deleted file mode 100644 index d3aa0ed8d1..0000000000 --- a/src/gui/mrview/tool/fixel.h +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ -#ifndef __gui_mrview_tool_fixel_fixelimage_h__ -#define __gui_mrview_tool_fixel_fixelimage_h__ - -#include - -#include "header.h" -#include "image.h" -#include "transform.h" - -#include "algo/loop.h" -#include "sparse/image.h" -#include "sparse/fixel_metric.h" -#include "fixel_format/helpers.h" - -#include "gui/mrview/displayable.h" -#include "gui/mrview/tool/vector.h" - - -namespace MR -{ - namespace GUI - { - namespace MRView - { - namespace Tool - { - - enum FixelColourType { CValue, Direction }; - enum FixelLengthType { Unity, Amplitude, LValue }; - - class AbstractFixel : public Displayable { - public: - AbstractFixel (const std::string&, Vector&); - ~AbstractFixel(); - - class Shader : public Displayable::Shader { - public: - Shader () : do_crop_to_slice (false), color_type (Direction), length_type (Amplitude) { } - std::string vertex_shader_source (const Displayable&) override; - std::string geometry_shader_source (const Displayable&) override; - std::string fragment_shader_source (const Displayable&) override; - virtual bool need_update (const Displayable&) const override; - virtual void update (const Displayable&) override; - protected: - bool do_crop_to_slice; - FixelColourType color_type; - FixelLengthType length_type; - } fixel_shader; - - - void render (const Projection& projection); - - void request_render_colourbar (DisplayableVisitor& visitor) override { - if(colour_type == CValue && show_colour_bar) - visitor.render_fixel_colourbar(*this); - } - - void load_image (); - void reload_dir_and_value_buffers (); - - void set_line_length_multiplier (float value) { - user_line_length_multiplier = value; - } - - float get_line_length_multiplier () const { - return user_line_length_multiplier; - } - - void set_line_thickness (float value) { - line_thickness = value; - } - - float get_line_thickenss () const { - return line_thickness; - } - - virtual void set_length_type (FixelLengthType value) { - length_type = value; - } - - FixelLengthType get_length_type () const { - return length_type; - } - - void set_colour_type (FixelColourType value) { - colour_type = value; - } - - FixelColourType get_colour_type () const { - return colour_type; - } - - bool virtual internal_buffers_dirty () const { - return false; - } - - void load_scaleby_vector_opts (ComboBoxWithErrorMsg& combo_box) const { - combo_box.clear (); - for (const auto& value_name: value_types) - combo_box.addItem (tr (value_name.c_str ())); - combo_box.setCurrentIndex (0); - } - - protected: - struct IntPointHasher { - size_t operator () (const std::array& v) const { - // This hashing function works best if the fixel image dimensions - // are bounded above by 2^10 x 2^10 x 2^10 = 1024 x 1024 x 1024 - return (v[0] + (v[1] << 10) + (v[2] << 20)); - } - }; - - virtual void load_image_buffer() = 0; - virtual void update_image_buffer() {} - virtual void request_update_interp_image_buffer (const Projection&) = 0; - void update_interp_image_buffer (const Projection&, const MR::Header&, const MR::Transform&); - - std::string filename; - MR::Header header; - std::vector value_types; - - std::unique_ptr> buffer_pos; - std::vector* buffer_dir; - std::vector* buffer_val; - - std::vector regular_grid_buffer_pos; - std::vector regular_grid_buffer_dir; - std::vector regular_grid_buffer_val; - - std::vector > > slice_fixel_indices; - std::vector > > slice_fixel_sizes; - std::vector > slice_fixel_counts; - - // Flattened buffer used when cropping to slice - // To support off-axis rendering, we maintain dict mapping voxels to buffer_pos indices - std::unordered_map , std::vector, IntPointHasher> voxel_to_indices_map; - - FixelLengthType length_type; - private: - Vector& fixel_tool; - GL::VertexBuffer vertex_buffer; - GL::VertexBuffer direction_buffer; - GL::VertexBuffer value_buffer; - GL::VertexArrayObject vertex_array_object; - - - GL::VertexArrayObject regular_grid_vao; - GL::VertexBuffer regular_grid_vertex_buffer; - GL::VertexBuffer regular_grid_dir_buffer; - GL::VertexBuffer regular_grid_val_buffer; - - float voxel_size_length_multipler; - float user_line_length_multiplier; - float line_thickness; - FixelColourType colour_type; - }; - - - // Wrapper to generically store fixel data - template class FixelType : public AbstractFixel - { - public: - FixelType (const std::string& filename, Vector& fixel_tool) : - AbstractFixel (filename, fixel_tool), - transform (header) { } - - protected: - std::unique_ptr fixel_data; - MR::Transform transform; - - void request_update_interp_image_buffer (const Projection& projection) override { - update_interp_image_buffer (projection, *fixel_data, transform); - } - }; - - typedef MR::Sparse::Image FixelSparseImageType; - typedef MR::Image FixelPackedImageType; - typedef MR::Image FixelIndexImageType; - - // Subclassed specialisations of template wrapper - // This is because loading of image data is dependent on particular buffer type - - class Fixel : public FixelType - { - public: - Fixel (const std::string& filename, Vector& fixel_tool) : - FixelType (filename, fixel_tool) - { - value_types = {"Unity", "Fixel size", "Associated value"}; - - buffer_pos.reset (new std::vector ()); - buffer_dir = &buffer_dir_store; - buffer_val = &buffer_val_store; - fixel_data.reset (new FixelSparseImageType (header)); - load_image (); - } - void load_image_buffer () override; - - private: - std::vector buffer_dir_store; - std::vector buffer_val_store; - }; - - - class PackedFixel : public FixelType - { - public: - PackedFixel (const std::string& filename, Vector& fixel_tool) : - FixelType (filename, fixel_tool) - { - value_types = {"Unity", "Fixel size"}; - - buffer_pos.reset (new std::vector ()); - buffer_dir = &buffer_dir_store; - buffer_val = &buffer_val_store; - fixel_data.reset (new FixelPackedImageType (header.get_image ())); - load_image (); - } - void load_image_buffer () override; - private: - std::vector buffer_dir_store; - std::vector buffer_val_store; - }; - - - class FixelFolder : public FixelType - { - public: - FixelFolder (const std::string& dirname, Vector& fixel_tool) : - FixelType (FixelFormat::find_index_header (dirname).name (), fixel_tool) - { - value_types = {"Unity"}; - - buffer_pos.reset (new std::vector ()); - fixel_data.reset (new FixelIndexImageType (header.get_image ())); - load_image (); - } - void load_image_buffer () override; - void set_length_type (FixelLengthType value) override; - virtual bool internal_buffers_dirty () const override { return buffer_dirty; } - protected: - void update_image_buffer () override; - private: - bool buffer_dirty; - std::string c_buffer_dir, c_buffer_val; - - std::map> buffer_dir_dict; - std::map> buffer_val_dict; - std::map> buffer_min_max_dict; - }; - - } - } - } -} -#endif diff --git a/src/gui/mrview/tool/vector.cpp b/src/gui/mrview/tool/vector.cpp deleted file mode 100644 index 6c1221b132..0000000000 --- a/src/gui/mrview/tool/vector.cpp +++ /dev/null @@ -1,722 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - -#include "gui/mrview/tool/vector.h" - -#include "mrtrix.h" -#include "gui/mrview/window.h" -#include "gui/mrview/tool/fixel.h" -#include "gui/dialog/file.h" -#include "gui/mrview/tool/list_model_base.h" -#include "math/rng.h" - -namespace MR -{ - namespace GUI - { - namespace MRView - { - namespace Tool - { - - - class Vector::Model : public ListModelBase - { - - public: - Model (QObject* parent) : - ListModelBase (parent) { } - - void add_items (std::vector& filenames, Vector& fixel_tool) { - - size_t old_size = items.size(); - for (size_t i = 0, N = filenames.size(); i < N; ++i) { - AbstractFixel* fixel_image(nullptr); - - try - { - if(Path::has_suffix (filenames[i], {".msf", ".msh"})) - fixel_image = new Fixel (filenames[i], fixel_tool); - else - fixel_image = new FixelFolder (Path::dirname (filenames[i]), fixel_tool); - } - catch (InvalidFixelDirectoryException &) - { - try - { - fixel_image = new PackedFixel (filenames[i], fixel_tool); - } - catch (InvalidImageException& e) - { - e.display(); - continue; - } - } - catch(InvalidImageException& e) - { - e.display(); - continue; - } - - items.push_back (std::unique_ptr (fixel_image)); - } - - beginInsertRows (QModelIndex(), old_size, items.size()); - endInsertRows (); - } - - AbstractFixel* get_fixel_image (QModelIndex& index) { - return dynamic_cast(items[index.row()].get()); - } - }; - - - - Vector::Vector (Dock* parent) : - Base (parent), - do_lock_to_grid (true), - do_crop_to_slice (true), - not_3D (true), - line_opacity (1.0) { - - VBoxLayout* main_box = new VBoxLayout (this); - HBoxLayout* layout = new HBoxLayout; - layout->setContentsMargins (0, 0, 0, 0); - layout->setSpacing (0); - - QPushButton* button = new QPushButton (this); - button->setToolTip (tr ("Open fixel image")); - button->setIcon (QIcon (":/open.svg")); - connect (button, SIGNAL (clicked()), this, SLOT (fixel_open_slot ())); - layout->addWidget (button, 1); - - button = new QPushButton (this); - button->setToolTip (tr ("Close fixel image")); - button->setIcon (QIcon (":/close.svg")); - connect (button, SIGNAL (clicked()), this, SLOT (fixel_close_slot ())); - layout->addWidget (button, 1); - - hide_all_button = new QPushButton (this); - hide_all_button->setToolTip (tr ("Hide all fixel images")); - hide_all_button->setIcon (QIcon (":/hide.svg")); - hide_all_button->setCheckable (true); - connect (hide_all_button, SIGNAL (clicked()), this, SLOT (hide_all_slot ())); - layout->addWidget (hide_all_button, 1); - - main_box->addLayout (layout, 0); - - fixel_list_view = new QListView (this); - fixel_list_view->setSelectionMode (QAbstractItemView::ExtendedSelection); - fixel_list_view->setDragEnabled (true); - fixel_list_view->viewport()->setAcceptDrops (true); - fixel_list_view->setDropIndicatorShown (true); - - fixel_list_model = new Model (this); - fixel_list_view->setModel (fixel_list_model); - - connect (fixel_list_model, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (toggle_shown_slot (const QModelIndex&, const QModelIndex&))); - - connect (fixel_list_view->selectionModel(), - SIGNAL (selectionChanged (const QItemSelection &, const QItemSelection &)), - SLOT (selection_changed_slot (const QItemSelection &, const QItemSelection &))); - - main_box->addWidget (fixel_list_view, 1); - - - HBoxLayout* hlayout = new HBoxLayout; - hlayout->setContentsMargins (0, 0, 0, 0); - hlayout->setSpacing (0); - - colour_combobox = new ComboBoxWithErrorMsg (0, " (variable) "); - hlayout->addWidget (new QLabel ("colour by ")); - main_box->addLayout (hlayout); - colour_combobox->addItem ("Value"); - colour_combobox->addItem ("Direction"); - hlayout->addWidget (colour_combobox, 0); - connect (colour_combobox, SIGNAL (activated(int)), this, SLOT (colour_changed_slot(int))); - - colourmap_option_group = new QGroupBox ("Colour map and scaling"); - main_box->addWidget (colourmap_option_group); - hlayout = new HBoxLayout; - colourmap_option_group->setLayout (hlayout); - - colourmap_button = new ColourMapButton (this, *this, false); - - hlayout->addWidget (colourmap_button); - - min_value = new AdjustButton (this); - connect (min_value, SIGNAL (valueChanged()), this, SLOT (on_set_scaling_slot())); - hlayout->addWidget (min_value); - - max_value = new AdjustButton (this); - connect (max_value, SIGNAL (valueChanged()), this, SLOT (on_set_scaling_slot())); - hlayout->addWidget (max_value); - - QGroupBox* threshold_box = new QGroupBox ("Thresholds"); - main_box->addWidget (threshold_box); - hlayout = new HBoxLayout; - threshold_box->setLayout (hlayout); - - threshold_lower_box = new QCheckBox (this); - connect (threshold_lower_box, SIGNAL (stateChanged(int)), this, SLOT (threshold_lower_changed(int))); - hlayout->addWidget (threshold_lower_box); - threshold_lower = new AdjustButton (this, 0.1); - connect (threshold_lower, SIGNAL (valueChanged()), this, SLOT (threshold_lower_value_changed())); - hlayout->addWidget (threshold_lower); - - threshold_upper_box = new QCheckBox (this); - hlayout->addWidget (threshold_upper_box); - threshold_upper = new AdjustButton (this, 0.1); - connect (threshold_upper_box, SIGNAL (stateChanged(int)), this, SLOT (threshold_upper_changed(int))); - connect (threshold_upper, SIGNAL (valueChanged()), this, SLOT (threshold_upper_value_changed())); - hlayout->addWidget (threshold_upper); - - hlayout = new HBoxLayout; - main_box->addLayout (hlayout); - hlayout->addWidget (new QLabel ("scale by ")); - - length_combobox = new ComboBoxWithErrorMsg (0, " (variable) "); - length_combobox->addItem ("Unity"); - length_combobox->addItem ("Fixel size"); - length_combobox->addItem ("Associated value"); - hlayout->addWidget (length_combobox, 0); - connect (length_combobox, SIGNAL (activated(int)), this, SLOT (length_type_slot(int))); - - hlayout = new HBoxLayout; - main_box->addLayout (hlayout); - hlayout->addWidget (new QLabel ("length multiplier")); - length_multiplier = new AdjustButton (this, 0.01); - length_multiplier->setMin (0.1); - length_multiplier->setValue (1.0); - connect (length_multiplier, SIGNAL (valueChanged()), this, SLOT (length_multiplier_slot())); - hlayout->addWidget (length_multiplier); - - GridLayout* default_opt_grid = new GridLayout; - line_thickness_slider = new QSlider (Qt::Horizontal); - line_thickness_slider->setRange (10,1000); - line_thickness_slider->setSliderPosition (200); - connect (line_thickness_slider, SIGNAL (valueChanged (int)), this, SLOT (line_thickness_slot (int))); - default_opt_grid->addWidget (new QLabel ("line thickness"), 0, 0); - default_opt_grid->addWidget (line_thickness_slider, 0, 1); - - opacity_slider = new QSlider (Qt::Horizontal); - opacity_slider->setRange (1, 1000); - opacity_slider->setSliderPosition (int (1000)); - connect (opacity_slider, SIGNAL (valueChanged (int)), this, SLOT (opacity_slot (int))); - default_opt_grid->addWidget (new QLabel ("opacity"), 1, 0); - default_opt_grid->addWidget (opacity_slider, 1, 1); - - lock_to_grid = new QGroupBox (tr("lock to grid")); - lock_to_grid->setCheckable (true); - lock_to_grid->setChecked (true); - connect (lock_to_grid, SIGNAL (clicked (bool)), this, SLOT (on_lock_to_grid_slot (bool))); - default_opt_grid->addWidget (lock_to_grid, 2, 0, 1, 2); - - crop_to_slice = new QGroupBox (tr("crop to slice")); - crop_to_slice->setCheckable (true); - crop_to_slice->setChecked (true); - connect (crop_to_slice, SIGNAL (clicked (bool)), this, SLOT (on_crop_to_slice_slot (bool))); - default_opt_grid->addWidget (crop_to_slice, 3, 0, 1, 2); - - main_box->addLayout (default_opt_grid, 0); - - main_box->addStretch (); - setMinimumSize (main_box->minimumSize()); - update_gui_controls (); - } - - - - void Vector::draw (const Projection& transform, bool is_3D, int, int) - { - ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; - not_3D = !is_3D; - for (int i = 0; i < fixel_list_model->rowCount(); ++i) { - if (fixel_list_model->items[i]->show && !hide_all_button->isChecked()) - dynamic_cast(fixel_list_model->items[i].get())->render (transform); - } - ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; - } - - - void Vector::draw_colourbars () - { - if(hide_all_button->isChecked()) return; - - for (size_t i = 0, N = fixel_list_model->rowCount(); i < N; ++i) { - if (fixel_list_model->items[i]->show) - dynamic_cast(fixel_list_model->items[i].get())->request_render_colourbar(*this); - } - } - - - size_t Vector::visible_number_colourbars () { - size_t total_visible(0); - - if(!hide_all_button->isChecked()) { - for (size_t i = 0, N = fixel_list_model->rowCount(); i < N; ++i) { - AbstractFixel* fixel = dynamic_cast(fixel_list_model->items[i].get()); - if (fixel && fixel->show && !ColourMap::maps[fixel->colourmap].special) - total_visible += 1; - } - } - - return total_visible; - } - - - - void Vector::render_fixel_colourbar(const Tool::AbstractFixel& fixel) - { - ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; - float min_value = fixel.use_discard_lower() ? - fixel.scaling_min_thresholded() : - fixel.scaling_min(); - - float max_value = fixel.use_discard_upper() ? - fixel.scaling_max_thresholded() : - fixel.scaling_max(); - - window().colourbar_renderer.render (fixel.colourmap, fixel.scale_inverted(), - min_value, max_value, - fixel.scaling_min(), fixel.display_range, - Eigen::Array3f { fixel.colour[0] / 255.0f, fixel.colour[1] / 255.0f, fixel.colour[2] / 255.0f }); - ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; - } - - - void Vector::fixel_open_slot () - { - std::vector list = Dialog::File::get_files (this, - "Select fixel images to open", - GUI::Dialog::File::image_filter_string); - add_images (list); - } - - - void Vector::add_images (std::vector &list) - { - if (list.empty()) - return; - size_t previous_size = fixel_list_model->rowCount(); - fixel_list_model->add_items (list, *this); - - // Some of the images may be invalid, so it could be the case that no images were added - size_t new_size = fixel_list_model->rowCount(); - if(previous_size < new_size) { - QModelIndex first = fixel_list_model->index (previous_size, 0, QModelIndex()); - QModelIndex last = fixel_list_model->index (new_size -1, 0, QModelIndex()); - fixel_list_view->selectionModel()->select (QItemSelection (first, last), QItemSelectionModel::Select); - update_gui_controls (); - } - } - - - void Vector::dropEvent (QDropEvent* event) - { - static constexpr int max_files = 32; - - const QMimeData* mimeData = event->mimeData(); - if (mimeData->hasUrls()) { - std::vector list; - QList urlList = mimeData->urls(); - for (int i = 0; i < urlList.size() && i < max_files; ++i) { - list.push_back (urlList.at (i).path().toUtf8().constData()); - } - try { - add_images (list); - } - catch (Exception& e) { - e.display(); - } - } - } - - - void Vector::fixel_close_slot () - { - QModelIndexList indexes = fixel_list_view->selectionModel()->selectedIndexes(); - while (indexes.size()) { - fixel_list_model->remove_item (indexes.first()); - indexes = fixel_list_view->selectionModel()->selectedIndexes(); - } - window().updateGL(); - } - - - void Vector::toggle_shown_slot (const QModelIndex& index, const QModelIndex& index2) - { - if (index.row() == index2.row()) { - fixel_list_view->setCurrentIndex(index); - } else { - for (size_t i = 0; i < fixel_list_model->items.size(); ++i) { - if (fixel_list_model->items[i]->show) { - fixel_list_view->setCurrentIndex (fixel_list_model->index (i, 0)); - break; - } - } - } - window().updateGL(); - } - - - void Vector::hide_all_slot () - { - window().updateGL(); - } - - - void Vector::update_gui_controls () - { - QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - size_t n_images (indices.size ()); - - colour_combobox->setEnabled (n_images); - colourmap_button->setEnabled (n_images); - - update_gui_scaling_controls (); - update_gui_threshold_controls (); - - if (!n_images) - return; - - int colourmap_index = -2; - for (size_t i = 0; i < n_images; ++i) { - AbstractFixel* fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[i])); - if (colourmap_index != int (fixel->colourmap)) { - if (colourmap_index == -2) - colourmap_index = fixel->colourmap; - else - colourmap_index = -1; - } - } - - // Not all colourmaps are added to this list; therefore need to find out - // how many menu elements were actually created by ColourMap::create_menu() - static size_t colourmap_count = 0; - if (!colourmap_count) { - for (size_t i = 0; MR::GUI::MRView::ColourMap::maps[i].name; ++i) { - if (!MR::GUI::MRView::ColourMap::maps[i].special) - ++colourmap_count; - } - } - - if (colourmap_index < 0) { - for (size_t i = 0; i != colourmap_count; ++i ) - colourmap_button->colourmap_actions[i]->setChecked (false); - } else { - colourmap_button->colourmap_actions[colourmap_index]->setChecked (true); - } - - - AbstractFixel* first_fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[0])); - const FixelLengthType length_type = first_fixel->get_length_type (); - const FixelColourType colour_type = first_fixel->get_colour_type (); - - length_combobox->setCurrentIndex (length_type); - colour_combobox->setCurrentIndex (colour_type); - colourmap_option_group->setEnabled (colour_type == CValue); - } - - - void Vector::update_gui_scaling_controls () - { - QModelIndexList indices = fixel_list_view->selectionModel ()->selectedIndexes (); - size_t n_images (indices.size ()); - - max_value->setEnabled (n_images); - min_value->setEnabled (n_images); - length_multiplier->setEnabled (n_images); - length_combobox->setEnabled (n_images == 1); - - if (!n_images) { - max_value->setValue (NAN); - min_value->setValue (NAN); - length_multiplier->setValue (NAN); - return; - } - - AbstractFixel* first_fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[0])); - - if (n_images == 1) - first_fixel->load_scaleby_vector_opts (*length_combobox); - - min_value->setRate (first_fixel->scaling_rate ()); - max_value->setRate (first_fixel->scaling_rate ()); - min_value->setValue (first_fixel->scaling_min ()); - max_value->setValue (first_fixel->scaling_max ()); - length_multiplier->setValue (first_fixel->get_line_length_multiplier ()); - line_thickness_slider->setValue (static_cast(first_fixel->get_line_thickenss () * 1.0e5f)); - } - - - void Vector::update_gui_threshold_controls () - { - QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - size_t n_images (indices.size ()); - - if (!n_images) { - threshold_lower->setValue (NAN); - threshold_upper->setValue (NAN); - return; - } - - AbstractFixel* first_fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[0])); - - if (!std::isfinite (first_fixel->lessthan)) - first_fixel->lessthan = first_fixel->intensity_min (); - if (!std::isfinite (first_fixel->greaterthan)) - first_fixel->greaterthan = first_fixel->intensity_max (); - - threshold_lower->setValue (first_fixel->lessthan); - threshold_lower->setRate (first_fixel->scaling_rate ()); - threshold_lower->setEnabled (first_fixel->use_discard_lower ()); - threshold_lower_box->setChecked (first_fixel->use_discard_lower ()); - - threshold_upper->setValue (first_fixel->greaterthan); - threshold_upper->setRate (first_fixel->scaling_rate ()); - threshold_upper->setEnabled (first_fixel->use_discard_lower ()); - threshold_upper_box->setChecked (first_fixel->use_discard_lower ()); - } - - - void Vector::opacity_slot (int opacity) - { - line_opacity = Math::pow2 (static_cast(opacity)) / 1.0e6f; - window().updateGL(); - } - - - void Vector::line_thickness_slot (int thickness) - { - QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->set_line_thickness (static_cast(thickness) / 1.0e5f); - window().updateGL(); - } - - - void Vector::length_multiplier_slot () - { - QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->set_line_length_multiplier (length_multiplier->value()); - window().updateGL(); - } - - - void Vector::length_type_slot (int selection) - { - QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - - for (int i = 0; i < indices.size(); ++i) { - const auto fixel = fixel_list_model->get_fixel_image (indices[i]); - fixel->set_length_type ((FixelLengthType)selection); - update_gui_scaling_controls (); - break; - } - - window ().updateGL (); - } - - - void Vector::selection_changed_slot (const QItemSelection &, const QItemSelection &) - { - update_gui_controls (); - } - - - void Vector::on_lock_to_grid_slot(bool is_checked) - { - do_lock_to_grid = is_checked; - window().updateGL(); - } - - - void Vector::on_crop_to_slice_slot (bool is_checked) - { - do_crop_to_slice = is_checked; - lock_to_grid->setEnabled(do_crop_to_slice); - - window().updateGL(); - } - - - void Vector::toggle_show_colour_bar (bool visible, const ColourMapButton&) - { - QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->show_colour_bar = visible; - window().updateGL(); - } - - - void Vector::selected_colourmap (size_t index, const ColourMapButton&) - { - QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - for (int i = 0; i < indices.size(); ++i) { - fixel_list_model->get_fixel_image (indices[i])->colourmap = index; - fixel_list_model->get_fixel_image (indices[i])->set_colour_type (CValue); - } - window().updateGL(); - } - - void Vector::selected_custom_colour(const QColor& colour, const ColourMapButton&) - { - if (colour.isValid()) { - QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - std::array c_colour{{GLubyte(colour.red()), GLubyte(colour.green()), GLubyte(colour.blue())}}; - for (int i = 0; i < indices.size(); ++i) { - fixel_list_model->get_fixel_image (indices[i])->set_colour (c_colour); - } - window().updateGL(); - } - } - - void Vector::reset_colourmap (const ColourMapButton&) - { - QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->reset_windowing (); - update_gui_controls (); - window().updateGL(); - } - - - void Vector::toggle_invert_colourmap (bool inverted, const ColourMapButton&) - { - QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->set_invert_scale (inverted); - window().updateGL(); - } - - - void Vector::colour_changed_slot (int selection) - { - QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - - switch (selection) { - case 0: { - colourmap_option_group->setEnabled (true); - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->set_colour_type (CValue); - break; - } - case 1: { - colourmap_option_group->setEnabled (false); - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->set_colour_type (Direction); - break; - } - default: - break; - } - window().updateGL(); - - } - - - void Vector::on_set_scaling_slot () - { - QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->set_windowing (min_value->value(), max_value->value()); - window().updateGL(); - } - - - void Vector::threshold_lower_changed (int) - { - if (threshold_lower_box->checkState() == Qt::PartiallyChecked) return; - threshold_lower->setEnabled (threshold_lower_box->isChecked()); - QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->set_use_discard_lower (threshold_lower_box->isChecked()); - window().updateGL(); - } - - - void Vector::threshold_upper_changed (int) - { - if (threshold_upper_box->checkState() == Qt::PartiallyChecked) return; - threshold_upper->setEnabled (threshold_upper_box->isChecked()); - QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->set_use_discard_upper (threshold_upper_box->isChecked()); - window().updateGL(); - } - - - void Vector::threshold_lower_value_changed () - { - if (threshold_lower_box->checkState() == Qt::PartiallyChecked) return; - if (threshold_lower_box->isChecked()) { - QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->lessthan = threshold_lower->value(); - window().updateGL(); - } - } - - - void Vector::threshold_upper_value_changed () - { - if (threshold_upper_box->checkState() == Qt::PartiallyChecked) return; - if (threshold_upper_box->isChecked()) { - QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->greaterthan = threshold_upper->value(); - window().updateGL(); - } - } - - - void Vector::add_commandline_options (MR::App::OptionList& options) - { - using namespace MR::App; - options - + OptionGroup ("Vector plot tool options") - - + Option ("vector.load", "Load the specified MRtrix sparse image file (.msf) into the fixel tool.") - + Argument ("image").type_image_in(); - } - - bool Vector::process_commandline_option (const MR::App::ParsedOption& opt) - { - if (opt.opt->is ("vector.load")) { - std::vector list (1, std::string(opt[0])); - try { fixel_list_model->add_items (list , *this); } - catch (Exception& E) { E.display(); } - return true; - } - - return false; - } - - - - - - } - } - } -} - - - - - diff --git a/src/gui/mrview/tool/vector.h b/src/gui/mrview/tool/vector.h deleted file mode 100644 index c098486dcc..0000000000 --- a/src/gui/mrview/tool/vector.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - -#ifndef __gui_mrview_tool_vector_h__ -#define __gui_mrview_tool_vector_h__ - -#include "gui/mrview/tool/base.h" -#include "gui/projection.h" -#include "gui/mrview/adjust_button.h" -#include "gui/mrview/combo_box_error.h" -#include "gui/mrview/colourmap_button.h" - -namespace MR -{ - namespace GUI - { - namespace MRView - { - namespace Tool - { - class Vector : public Base, public ColourMapButtonObserver, public DisplayableVisitor - { - Q_OBJECT - - public: - class Model; - - Vector (Dock* parent); - - virtual ~Vector () {} - - void draw (const Projection& transform, bool is_3D, int axis, int slice) override; - void draw_colourbars () override; - size_t visible_number_colourbars () override; - void render_fixel_colourbar(const Tool::AbstractFixel& fixel) override; - - static void add_commandline_options (MR::App::OptionList& options); - virtual bool process_commandline_option (const MR::App::ParsedOption& opt) override; - - void selected_colourmap(size_t index, const ColourMapButton&) override; - void selected_custom_colour(const QColor& colour, const ColourMapButton&) override; - void toggle_show_colour_bar(bool, const ColourMapButton&) override; - void toggle_invert_colourmap(bool, const ColourMapButton&) override; - void reset_colourmap(const ColourMapButton&) override; - - QPushButton* hide_all_button; - bool do_lock_to_grid, do_crop_to_slice; - bool not_3D; - float line_opacity; - Model* fixel_list_model; - QListView* fixel_list_view; - - - private slots: - void fixel_open_slot (); - void fixel_close_slot (); - void toggle_shown_slot (const QModelIndex&, const QModelIndex&); - void hide_all_slot (); - void on_lock_to_grid_slot (bool is_checked); - void on_crop_to_slice_slot (bool is_checked); - void opacity_slot (int opacity); - void line_thickness_slot (int thickness); - void length_multiplier_slot (); - void length_type_slot (int); - void selection_changed_slot (const QItemSelection &, const QItemSelection &); - void colour_changed_slot (int); - void on_set_scaling_slot (); - void threshold_lower_changed (int unused); - void threshold_upper_changed (int unused); - void threshold_lower_value_changed (); - void threshold_upper_value_changed (); - - protected: - ComboBoxWithErrorMsg *colour_combobox; - - QGroupBox *colourmap_option_group; - QAction *show_colour_bar, *invert_scale; - ColourMapButton *colourmap_button; - - AdjustButton *min_value, *max_value; - AdjustButton *threshold_lower, *threshold_upper; - QCheckBox *threshold_upper_box, *threshold_lower_box; - - ComboBoxWithErrorMsg *length_combobox; - AdjustButton *length_multiplier; - - QSlider *line_thickness_slider; - QSlider *opacity_slider; - - QGroupBox *lock_to_grid, *crop_to_slice; - - void add_images (std::vector& list); - void dropEvent (QDropEvent* event) override; - - private: - void update_gui_controls (); - void update_gui_scaling_controls (); - void update_gui_threshold_controls (); - }; - } - } - } -} - -#endif - - - - From fd690bcad31c53699d44520f171e29ac1eb69a6b Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sat, 23 Jul 2016 01:06:36 +1000 Subject: [PATCH 034/723] New command: mrhistmatch This command determines a non-linear mapping in order to match the histogram of one image to another image. One particular use case of interest is taking the reciprocal of the b=0 image, then matching the histogram to a T1 image, to produce a pseudo-T1 DWI that can be used for registration to T1 using the sum-of-squares metric. This commit also includes a substantial amount of work in homogenizing the generation and use of histograms by different commands; previously some functionality was duplicated in different areas, sometimes with minor differences. Code branching design of mrstats has changed; hopefully this will make it more amenable to improvements in the future. Methods that generate a histogram will now by default use the Freedman-Diaconis rule for determining an appropriate granularity for the histogram. Closes #671. --- cmd/fixelstats.cpp | 57 +++--- cmd/mrhistmatch.cpp | 103 ++++++++++ cmd/mrstats.cpp | 223 +++++++++------------ cmd/mrthreshold.cpp | 22 ++- docs/reference/commands/fixelstats.rst | 9 +- docs/reference/commands/mrhistmatch.rst | 65 ++++++ docs/reference/commands/mrstats.rst | 14 +- docs/reference/commands/mrthreshold.rst | 2 +- docs/reference/commands_list.rst | 6 +- lib/algo/histogram.cpp | 184 +++++++++++++++++ lib/algo/histogram.h | 252 +++++++++++++++++------- lib/stats.cpp | 9 - lib/stats.h | 87 +++----- 13 files changed, 713 insertions(+), 320 deletions(-) create mode 100644 cmd/mrhistmatch.cpp create mode 100644 docs/reference/commands/mrhistmatch.rst create mode 100644 lib/algo/histogram.cpp diff --git a/cmd/fixelstats.cpp b/cmd/fixelstats.cpp index 8746348828..1284d8f5fd 100644 --- a/cmd/fixelstats.cpp +++ b/cmd/fixelstats.cpp @@ -16,6 +16,7 @@ #include "command.h" #include "progressbar.h" +#include "algo/histogram.h" #include "algo/loop.h" #include "stats.h" @@ -42,7 +43,8 @@ void usage () + Argument ("input", "the input fixel image.").type_image_in (); OPTIONS - + Stats::Options; + + Stats::Options + + Algo::Histogram::Options; } @@ -50,17 +52,14 @@ void run () { Sparse::Image input (argument[0]); - std::unique_ptr dumpstream, hist_stream, position_stream; + auto opt = get_options("mask"); + std::unique_ptr > mask_ptr; + if (opt.size()) { + mask_ptr.reset (new Sparse::Image (opt[0][0])); + check_dimensions (input, *mask_ptr); + } - auto opt = get_options ("histogram"); - if (opt.size()) - hist_stream.reset (new File::OFStream (opt[0][0])); - - int nbins = DEFAULT_HISTOGRAM_BINS; - opt = get_options ("bins"); - if (opt.size()) - nbins = opt[0][0]; - Stats::CalibrateHistogram calibrate (nbins); + std::unique_ptr dumpstream, position_stream; opt = get_options ("dump"); if (opt.size()) @@ -77,15 +76,17 @@ void run () bool header_shown (!App::log_level || fields.size()); - opt = get_options("mask"); - std::unique_ptr > mask_ptr; - if (opt.size()) { - mask_ptr.reset (new Sparse::Image (opt[0][0])); - check_dimensions (input, *mask_ptr); - } + Stats::Stats stats (false); + if (dumpstream) + stats.dump_to (*dumpstream); - if (hist_stream) { + opt = get_options ("histogram"); + std::string histogram_path; + if (opt.size()) { + histogram_path = std::string(opt[0][0]); + const size_t nbins = get_option_value ("bins", 0); + Algo::Histogram::Calibrator calibrate (nbins, false); for (auto i = Loop (input) (input); i; ++i) { if (mask_ptr) { assign_pos_of (input).to (*mask_ptr); @@ -101,16 +102,11 @@ void run () } } } - calibrate.init (*hist_stream); - } - - Stats::Stats stats (false); - - if (dumpstream) - stats.dump_to (*dumpstream); - - if (hist_stream) + calibrate.finalize (1); stats.generate_histogram (calibrate); + } else if (get_options ("bins").size()) { + WARN ("Option -bins ignored as -histogram was not specified"); + } for (auto i = Loop (input) (input); i; ++i) { if (mask_ptr) { @@ -135,7 +131,10 @@ void run () stats.print (input, fields); - if (hist_stream) - stats.write_histogram (*hist_stream); + if (histogram_path.size()) { + File::OFStream stream (histogram_path); + stats.write_histogram_header (stream); + stats.write_histogram_data (stream); + } } diff --git a/cmd/mrhistmatch.cpp b/cmd/mrhistmatch.cpp new file mode 100644 index 0000000000..f1440bc783 --- /dev/null +++ b/cmd/mrhistmatch.cpp @@ -0,0 +1,103 @@ + +#include +#include + +#include "command.h" +#include "datatype.h" +#include "header.h" +#include "image.h" +#include "algo/histogram.h" +#include "algo/loop.h" + + +using namespace MR; +using namespace App; + +void usage () { + + AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au"; + + DESCRIPTION + + "modify the intensities of one image to match the histogram of another via a non-linear transform."; + + ARGUMENTS + + 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 + + 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 () + + // TODO Remove before release + + Option ("cdfs", "output the histogram CDFs to a text file (for debugging).") + + Argument ("path").type_file_out () + + + Option ("bins", "the number of bins to use to generate the histograms") + + Argument ("num").type_integer (2); + +} + + + +typedef default_type value_type; + + + +void run () +{ + + auto input = Image::open (argument[0]); + auto target = Image::open (argument[1]); + + if (input.ndim() > 3 || target.ndim() > 3) + throw Exception ("mrhistmatch currently only works on 3D images"); + + const size_t nbins = get_option_value ("bins", 0); + + Image mask_input, mask_target; + + auto opt = get_options ("mask_input"); + if (opt.size()) { + mask_input = Image::open (opt[0][0]); + check_dimensions (input, mask_input); + } + opt = get_options ("mask_target"); + if (opt.size()) { + mask_target = Image::open (opt[0][0]); + check_dimensions (target, mask_target); + } + + 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); + + // Now need to find the conversion function to map the input histogram to the target one + + // TODO Move this to a header somewhere + + Algo::Histogram::Matcher matcher (hist_input, hist_target); + + // Generate the output image + Header H (input); + H.datatype() = DataType::Float32; + H.datatype().set_byte_order_native(); + auto output = Image::create (argument[2], H); + for (auto l = Loop(input) (input, output); l; ++l) { + if (std::isfinite (input.value())) { + output.value() = matcher (input.value()); + } else { + output.value() = 0.0; + } + } + +} + diff --git a/cmd/mrstats.cpp b/cmd/mrstats.cpp index dafbe49866..4c22b5c2b7 100644 --- a/cmd/mrstats.cpp +++ b/cmd/mrstats.cpp @@ -22,6 +22,7 @@ #include "image.h" #include "image_helpers.h" #include "memory.h" +#include "algo/histogram.h" #include "algo/loop.h" #include "file/ofstream.h" #include "stats.h" @@ -46,8 +47,14 @@ ARGUMENTS OPTIONS + Stats::Options + + Algo::Histogram::Options + + + OptionGroup ("Additional options for mrstats") + + + Option ("ignorezero", "ignore zero-valued input voxels.") + + Option ("voxel", - "only perform computation within the specified voxel, supplied as a " + "only perform computation within the specified voxel(s), supplied as a " "comma-separated vector of 3 integer values (multiple voxels can be included).") .allow_multiple() + Argument ("pos").type_sequence_int () @@ -99,6 +106,7 @@ class Volume_loop + void run () { @@ -107,24 +115,72 @@ void run () auto data = header.get_image(); if (data.ndim() > 4) throw Exception ("mrstats is not designed to handle images greater than 4D"); + const bool ignorezero = get_options("ignorezero").size(); auto inner_loop = Loop(0, 3); std::unique_ptr dumpstream, hist_stream, position_stream; - auto opt = get_options ("histogram"); + auto opt = get_options ("voxel"); + std::vector voxels; + if (opt.size()) { + voxels.resize (opt.size()); + for (size_t i = 0; i < opt.size(); ++i) { + std::vector x = parse_ints (opt[i][0]); + if (x.size() != 3) + throw Exception ("vector positions must be supplied as x,y,z"); + if (x[0] < 0 || x[0] >= data.size (0) || + x[1] < 0 || x[1] >= data.size (1) || + x[2] < 0 || x[2] >= data.size (2)) + throw Exception ("voxel at [ " + str (x[0]) + " " + str (x[1]) + + " " + str (x[2]) + " ] is out of bounds"); + voxels[i] = { x[0], x[1], x[2] }; + } + } + + opt = get_options ("mask"); + Image mask; + if (opt.size()) { + if (voxels.size()) + throw Exception ("cannot use mask with -voxel option"); + mask = Image::open (opt[0][0]); + check_dimensions (mask, data, 0, 3); + } + + std::unique_ptr calibrate; + opt = get_options ("histogram"); if (opt.size()) { if (is_complex) throw Exception ("histogram generation not supported for complex data types"); hist_stream.reset (new File::OFStream (opt[0][0])); + const size_t nbins = get_option_value ("bins", 0); + calibrate.reset (new Algo::Histogram::Calibrator (nbins, ignorezero)); + ProgressBar progress ("Calibrating histogram", voxels.size() ? (voxels.size() * (data.ndim() > 3 ? data.size(3) : 1)) : voxel_count (data)); + for (auto i = Volume_loop (data); i; ++i) { + if (mask.valid()) { + for (auto j = inner_loop (mask, data); j; ++j) { + if (mask.value()) + (*calibrate) (complex_type(data.value()).real()); + ++progress; + } + } else if (voxels.size()) { + for (size_t i = 0; i < voxels.size(); ++i) { + data.index(0) = voxels[i][0]; + data.index(1) = voxels[i][1]; + data.index(2) = voxels[i][2]; + (*calibrate) (complex_type(data.value()).real()); + } + } else { + for (auto j = inner_loop (data); j; ++j) + (*calibrate) (complex_type(data.value()).real()); + ++progress; + } + } + calibrate->finalize (data.ndim() > 3 ? data.size(3) : 1); + } else if (get_options("bins").size()) { + WARN ("Option -bins ignored as -histogram was not specified"); } - int nbins = DEFAULT_HISTOGRAM_BINS; - opt = get_options ("bins"); - if (opt.size()) - nbins = opt[0][0]; - Stats::CalibrateHistogram calibrate (nbins); - opt = get_options ("dump"); if (opt.size()) dumpstream.reset (new File::OFStream (opt[0][0])); @@ -140,37 +196,20 @@ void run () bool header_shown (!App::log_level || fields.size()); - auto voxels = get_options ("voxel"); - - opt = get_options ("mask"); - if (opt.size()) { // within mask: - - if (voxels.size()) - throw Exception ("cannot use mask with -voxel option"); + bool first_volume = true; + for (auto i = Volume_loop (data); i; ++i) { + Stats::Stats stats (is_complex); - auto mask = Image::open (opt[0][0]); - check_dimensions (mask, data, 0, 3); + if (dumpstream) + stats.dump_to (*dumpstream); if (hist_stream) { - ProgressBar progress ("calibrating histogram", voxel_count (data)); - for (auto i = Volume_loop (data); i; ++i) { - for (auto j = inner_loop (mask, data); j; ++j) { - if (mask.value()) - calibrate (complex_type(data.value()).real()); - ++progress; - } - } - calibrate.init (*hist_stream); + stats.generate_histogram (*calibrate); + if (first_volume) + stats.write_histogram_header (*hist_stream); } - for (auto i = Volume_loop (data); i; ++i) { - Stats::Stats stats (is_complex); - - if (dumpstream) - stats.dump_to (*dumpstream); - - if (hist_stream) - stats.generate_histogram (calibrate); + if (mask.valid()) { for (auto j = inner_loop (mask, data); j; ++j) { if (mask.value() > 0.5) { @@ -183,43 +222,21 @@ void run () } } - if (!header_shown) - Stats::print_header (is_complex); - header_shown = true; - - stats.print (data, fields); - - if (hist_stream) - stats.write_histogram (*hist_stream); - } - - return; - } - - - + } else if (voxels.size()) { - if (!voxels.size()) { // whole data set: - - if (hist_stream) { - ProgressBar progress ("calibrating histogram", voxel_count (data)); - for (auto i = Volume_loop (data); i; ++i) { - for (auto j = inner_loop (data); j; ++j) { - calibrate (complex_type(data.value()).real()); - ++progress; + for (size_t i = 0; i < voxels.size(); ++i) { + data.index(0) = voxels[i][0]; + data.index(1) = voxels[i][1]; + data.index(2) = voxels[i][2]; + stats (data.value()); + if (position_stream) { + for (size_t i = 0; i < data.ndim(); ++i) + *position_stream << data.index(i) << " "; + *position_stream << "\n"; } } - calibrate.init (*hist_stream); - } - for (auto l = Volume_loop (data); l; ++l) { - Stats::Stats stats (is_complex); - - if (dumpstream) - stats.dump_to (*dumpstream); - - if (hist_stream) - stats.generate_histogram (calibrate); + } else { for (auto j = inner_loop (data); j; ++j) { stats (data.value()); @@ -230,70 +247,6 @@ void run () } } - if (!header_shown) - Stats::print_header (is_complex); - header_shown = true; - - stats.print (data, fields); - - if (hist_stream) - stats.write_histogram (*hist_stream); - } - - return; - } - - - - - // voxels: - - std::vector voxel (voxels.size()); - for (size_t i = 0; i < voxels.size(); ++i) { - std::vector x = parse_ints (voxels[i][0]); - if (x.size() != 3) - throw Exception ("vector positions must be supplied as x,y,z"); - if (x[0] < 0 || x[0] >= data.size (0) || - x[1] < 0 || x[1] >= data.size (1) || - x[2] < 0 || x[2] >= data.size (2)) - throw Exception ("voxel at [ " + str (x[0]) + " " + str (x[1]) - + " " + str (x[2]) + " ] is out of bounds"); - voxel[i] = { x[0], x[1], x[2] }; - } - - if (hist_stream) { - ProgressBar progress ("calibrating histogram", voxel.size()); - for (auto i = Volume_loop (data); i; ++i) { - for (size_t i = 0; i < voxel.size(); ++i) { - data.index(0) = voxel[i][0]; - data.index(1) = voxel[i][1]; - data.index(2) = voxel[i][2]; - calibrate (complex_type(data.value()).real()); - ++progress; - } - } - calibrate.init (*hist_stream); - } - - for (auto i = Volume_loop (data); i; ++i) { - Stats::Stats stats (is_complex); - - if (dumpstream) - stats.dump_to (*dumpstream); - - if (hist_stream) - stats.generate_histogram (calibrate); - - for (size_t i = 0; i < voxel.size(); ++i) { - data.index(0) = voxel[i][0]; - data.index(1) = voxel[i][1]; - data.index(2) = voxel[i][2]; - stats (data.value()); - if (position_stream) { - for (size_t i = 0; i < data.ndim(); ++i) - *position_stream << data.index(i) << " "; - *position_stream << "\n"; - } } if (!header_shown) @@ -303,7 +256,11 @@ void run () stats.print (data, fields); if (hist_stream) - stats.write_histogram (*hist_stream); - } + stats.write_histogram_data (*hist_stream); + + first_volume = false; + + } // End looping over volumes + } diff --git a/cmd/mrthreshold.cpp b/cmd/mrthreshold.cpp index 6245679a9b..deb1132043 100644 --- a/cmd/mrthreshold.cpp +++ b/cmd/mrthreshold.cpp @@ -42,7 +42,7 @@ void usage () "or using a histogram-based analysis to cut out the background."; REFERENCES - + "* If not using the -abs option:\n" + + "* If not using the -histogram option or any manual thresholding option:\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"; @@ -230,15 +230,19 @@ void run () } } else { + Image mask; + opt = get_options ("mask"); + if (opt.size()) + mask = Image::open (opt[0][0]); if (use_histogram) { - Histogram hist (in); - threshold_value = hist.first_min(); - } - else if (std::isnan (threshold_value)) { - Image mask; - opt = get_options ("mask"); - if (opt.size()) - mask = Image::open (opt[0][0]); + if (mask.valid()) { + auto hist = Algo::Histogram::generate (in, mask, 0); + threshold_value = hist.first_min(); + } else { + auto hist = Algo::Histogram::generate (in, 0); + threshold_value = hist.first_min(); + } + } else if (std::isnan (threshold_value)) { threshold_value = Filter::estimate_optimal_threshold (in, mask); } diff --git a/docs/reference/commands/fixelstats.rst b/docs/reference/commands/fixelstats.rst index c1c8efb310..df9fccad96 100644 --- a/docs/reference/commands/fixelstats.rst +++ b/docs/reference/commands/fixelstats.rst @@ -27,11 +27,14 @@ Statistics options - **-mask image** only perform computation within the specified binary mask image. -- **-histogram file** generate histogram of intensities and store in specified text file. Note that the first line of the histogram gives the centre of the bins. +- **-dump file** dump the voxel intensities to a text file. -- **-bins num** the number of bins to use to generate the histogram (default = 100). +Histogram generation options +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- **-dump file** dump the voxel intensities to a text file. +- **-histogram file** generate histogram of intensities and store in specified text file. Note that the first line of the histogram gives the centre of the bins. + +- **-bins num** Manually set the number of bins to use to generate the histogram. Standard options ^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/mrhistmatch.rst b/docs/reference/commands/mrhistmatch.rst new file mode 100644 index 0000000000..46ea1abbb3 --- /dev/null +++ b/docs/reference/commands/mrhistmatch.rst @@ -0,0 +1,65 @@ +.. _mrhistmatch: + +mrhistmatch +=========== + +Synopsis +-------- + +:: + + mrhistmatch [ options ] input target output + +- *input*: the input image to be modified +- *target*: the input image from which to derive the target histogram +- *output*: the output image + +Description +----------- + +modify the intensities of one image to match the histogram of another via a non-linear transform. + +Options +------- + +- **-mask_input image** only generate input histogram based on a specified binary mask image + +- **-mask_target image** only generate target histogram based on a specified binary mask image + +- **-cdfs path** output the histogram CDFs to a text file (for debugging). + +- **-bins num** the number of bins to use to generate the histograms + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) + +- **-failonwarn** terminate program if a warning is produced + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +-------------- + + + +**Author:** Robert E. Smith (robert.smith@florey.edu.au + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + diff --git a/docs/reference/commands/mrstats.rst b/docs/reference/commands/mrstats.rst index a0899ea3dc..f1b4d4e6d9 100644 --- a/docs/reference/commands/mrstats.rst +++ b/docs/reference/commands/mrstats.rst @@ -27,13 +27,21 @@ Statistics options - **-mask image** only perform computation within the specified binary mask image. +- **-dump file** dump the voxel intensities to a text file. + +Histogram generation options +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + - **-histogram file** generate histogram of intensities and store in specified text file. Note that the first line of the histogram gives the centre of the bins. -- **-bins num** the number of bins to use to generate the histogram (default = 100). +- **-bins num** Manually set the number of bins to use to generate the histogram. -- **-dump file** dump the voxel intensities to a text file. +Additional options for mrstats +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-ignorezero** ignore zero-valued input voxels. -- **-voxel pos** only perform computation within the specified voxel, supplied as a comma-separated vector of 3 integer values (multiple voxels can be included). +- **-voxel pos** only perform computation within the specified voxel(s), supplied as a comma-separated vector of 3 integer values (multiple voxels can be included). - **-position file** dump the position of the voxels in the mask to a text file. diff --git a/docs/reference/commands/mrthreshold.rst b/docs/reference/commands/mrthreshold.rst index 306c73e928..e4011fe442 100644 --- a/docs/reference/commands/mrthreshold.rst +++ b/docs/reference/commands/mrthreshold.rst @@ -65,7 +65,7 @@ Standard options References ^^^^^^^^^^ -* If not using the -abs option: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 the -histogram option or any manual thresholding option: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 -------------- diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index 50bb905bf1..b13af298d3 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -107,6 +107,8 @@ List of MRtrix3 commands commands/mrfilter + commands/mrhistmatch + commands/mrinfo commands/mrmath @@ -165,10 +167,10 @@ List of MRtrix3 commands commands/tcksample - commands/tcksift2 - commands/tcksift + commands/tcksift2 + commands/tckstats commands/tensor2metric diff --git a/lib/algo/histogram.cpp b/lib/algo/histogram.cpp new file mode 100644 index 0000000000..65a49c841e --- /dev/null +++ b/lib/algo/histogram.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "algo/histogram.h" + +namespace MR +{ + namespace Algo + { + namespace Histogram + { + + + using namespace App; + + const OptionGroup Options = OptionGroup ("Histogram generation options") + + + Option ("histogram", + "generate histogram of intensities and store in specified text file. Note " + "that the first line of the histogram gives the centre of the bins.") + + Argument ("file").type_file_out () + + + Option ("bins", + "Manually set the number of bins to use to generate the histogram.") + + Argument ("num").type_integer (2); + + + + + void Calibrator::finalize (const size_t num_volumes) + { + if (!std::isfinite (bin_width)) { + if (num_bins) { + bin_width = (max - min) / default_type(num_bins); + } else { + // Freedman-Diaconis rule for selecting bin size for a histogram + // Sometimes data from multiple volumes are used for calibration, but + // histograms are generated for individual volumes + // Need to adjust the bin width accordingly... kinda ugly hack + // Will need to revisit if mrstats gets capability to compute statistics across all volumes rather than splitting + bin_width = 2.0 * get_iqr() * std::pow (data.size() / num_volumes, -1.0/3.0); + std::vector().swap (data); // No longer required; free the memory used + // Now set the number of bins, and recalculate the bin width, to ensure + // evenly-spaced bins from min to max + num_bins = std::round ((max - min) / get_bin_width()); + bin_width = (max - min) / default_type(num_bins); + } + } + } + + + + default_type Calibrator::get_iqr() { + assert (data.size()); + const size_t lower_index = std::round (0.25*data.size()); + std::nth_element (data.begin(), data.begin() + lower_index, data.end()); + const default_type lower = data[data.size()/4]; + const size_t upper_index = std::round (0.75*data.size()); + std::nth_element (data.begin(), data.begin() + upper_index, data.end()); + const default_type upper = data[upper_index]; + return (upper - lower); + } + + + + + + + Data::cdf_type Data::cdf() const + { + cdf_type result (list.size()); + size_t count = 0; + for (size_t i = 0; i != size_t(list.size()); ++i) { + count += list[i]; + result[i] = count; + } + result /= count; + return result; + } + + + + default_type Data::first_min () const + { + ssize_t p1 = 0; + while (list[p1] <= list[p1+1] && p1+2 < list.size()) + ++p1; + for (ssize_t p = p1; p < list.size(); ++p) { + if (2*list[p] < list[p1]) + break; + if (list[p] >= list[p1]) + p1 = p; + } + + ssize_t m1 (p1+1); + while (list[m1] >= list[m1+1] && m1+2 < list.size()) + ++m1; + for (ssize_t m = m1; m < list.size(); ++m) { + if (list[m] > 2*list[m1]) + break; + if (list[m] <= list[m1]) + m1 = m; + } + + return info.get_min() + (info.get_bin_width() * (m1 + 0.5)); + } + + + + default_type Data::entropy () const { + size_t totalFrequency = 0; + for (size_t i = 0; i < size_t(list.size()); i++) + totalFrequency += list[i]; + default_type imageEntropy = 0; + for (size_t i = 0; i < size_t(list.size()); i++){ + const default_type probability = static_cast(list[i]) / static_cast(totalFrequency); + if (probability > 0.99 / totalFrequency) + imageEntropy += -probability * log(probability); + } + return imageEntropy; + } + + + + + + + Matcher::Matcher (const Data& input, const Data& target) : + calib_input (input .get_calibration()), + calib_target (target.get_calibration()) + { + // Need to have the CDF for each of the two histograms + const auto cdf_input = input.cdf(); + const auto cdf_target = target.cdf(); + + // Each index in the input CDF needs to map to a (floating-point) index in the target CDF + // (linearly approximate the index that would result in the same value in the target CDF) + mapping = vector_type::Zero (cdf_input.size() + 1); + size_t upper_target_index = 1; + for (size_t input_index = 1; input_index != size_t(cdf_input.size()); ++input_index) { + while (upper_target_index < size_t(cdf_target.size()) && cdf_target[upper_target_index] < cdf_input[input_index]) + ++upper_target_index; + const size_t lower_target_index = upper_target_index - 1; + const default_type mu = (cdf_input[input_index] - cdf_target[lower_target_index]) / (cdf_target[upper_target_index] - cdf_target[lower_target_index]); + mapping[input_index] = lower_target_index + mu; + } + } + + + + default_type Matcher::operator() (const default_type in) const + { + const default_type input_bin_float = (in - calib_input.get_min()) / calib_input.get_bin_width(); + default_type output_pos; + if (input_bin_float < 0.0) { + output_pos = 0.0; + } else if (input_bin_float >= default_type(calib_input.get_num_bins())) { + output_pos = default_type(calib_input.get_num_bins()); + } else { + const size_t lower = std::floor (input_bin_float); + const default_type mu = input_bin_float - lower; + output_pos = ((1.0 - mu) * mapping[lower]) + (mu * mapping[lower+1]); + } + return calib_target.get_min() + (output_pos * calib_target.get_bin_width()); + } + + + + + } + } +} diff --git a/lib/algo/histogram.h b/lib/algo/histogram.h index eb5a0e10d1..cbd5fe1065 100644 --- a/lib/algo/histogram.h +++ b/lib/algo/histogram.h @@ -13,109 +13,213 @@ * */ -#ifndef __image_histogram_h__ -#define __image_histogram_h__ +#ifndef __algo_histogram_h__ +#define __algo_histogram_h__ #include +#include "image_helpers.h" #include "algo/loop.h" -#include "algo/min_max.h" namespace MR { - - template class Histogram + namespace Algo + { + namespace Histogram { - public: - typedef typename Set::value_type value_type; - Histogram (Set& D, const size_t num_buckets=100) { - if (num_buckets < 10) - throw Exception ("Error initialising histogram: number of buckets must be greater than 10"); - INFO ("Initialising histogram with " + str (num_buckets) + " buckets"); - list.resize (num_buckets); + extern const App::OptionGroup Options; - value_type min, max; - min_max (D, min, max); - value_type step = (max-min) / value_type (num_buckets); + class Calibrator + { - for (size_t n = 0; n < list.size(); n++) - list[n].value = min + step * (n + 0.5); + public: + Calibrator (const size_t number_of_bins = 0, const bool ignorezero = false) : + min (std::numeric_limits::infinity()), + max (-std::numeric_limits::infinity()), + bin_width (NaN), + num_bins (number_of_bins), + ignore_zero (ignorezero) { } - for (auto l = Loop("building histogram of \"" + shorten (D.name()) + "\"", D) (D); l; ++l) { - const value_type val = D.value(); - if (std::isfinite (val) && val != 0.0) { - size_t pos = size_t ( (val-min) /step); - if (pos >= list.size()) pos = list.size()-1; - list[pos].frequency++; + template + bool operator() (const value_type val) { + if (std::isfinite (val) && !(ignore_zero && val == 0.0)) { + min = std::min (min, default_type(val)); + max = std::max (max, default_type(val)); + if (!num_bins) + data.push_back (default_type(val)); } + return true; } - } + void finalize (const size_t num_volumes); - size_t frequency (size_t index) const { - return list[index].frequency; - } - value_type value (size_t index) const { - return list[index].value; - } - size_t num () const { - return list.size(); - } - value_type first_min () const { - size_t p1 = 0; - while (list[p1].frequency <= list[p1+1].frequency && p1+2 < list.size()) - ++p1; - for (size_t p = p1; p < list.size(); ++p) { - if (2*list[p].frequency < list[p1].frequency) - break; - if (list[p].frequency >= list[p1].frequency) - p1 = p; - } + default_type get_bin_width() const { return bin_width; } + size_t get_num_bins() const { return num_bins; } + default_type get_min() const { return min; } + default_type get_max() const { return max; } + bool get_ignore_zero() const { return ignore_zero; } + + + private: + default_type min, max, bin_width; + size_t num_bins; + const bool ignore_zero; + std::vector data; + + default_type get_iqr(); + + }; - size_t m1 (p1+1); - while (list[m1].frequency >= list[m1+1].frequency && m1+2 < list.size()) - ++m1; - for (size_t m = m1; m < list.size(); ++m) { - if (list[m].frequency > 2*list[m1].frequency) - break; - if (list[m].frequency <= list[m1].frequency) - m1 = m; + + + + class Data + { + public: + + typedef Eigen::Array vector_type; + typedef Eigen::Array cdf_type; + + Data (const Calibrator& calibrate) : + info (calibrate), + list (vector_type::Zero (info.get_num_bins())) { } + + template + bool operator() (const value_type val) { + if (std::isfinite (val) && !(info.get_ignore_zero() && val == 0.0)) { + const size_t pos = bin (val); + if (pos != size_t(list.size())) + ++list[pos]; + } + return true; } - return list[m1].value; - } + template + size_t bin (const value_type val) const { + size_t pos = std::floor ((val - info.get_min()) / info.get_bin_width()); + if (pos < 0) return size(); + if (pos > size_t(list.size())) return size(); + return pos; + } - float entropy () const { - int totalFrequency = 0; - for (size_t i = 0; i < list.size(); i++) - totalFrequency += list[i].frequency; - float imageEntropy = 0; - for (size_t i = 0; i < list.size(); i++){ - double probability = static_cast(list[i].frequency) / static_cast(totalFrequency); - if (probability > 0.99 / totalFrequency) - imageEntropy += -probability * log(probability); + size_t operator[] (const size_t index) const { + assert (index < size_t(list.size())); + return list[index]; + } + size_t size() const { + return list.size(); } - return imageEntropy; + default_type get_bin_centre (const size_t i) const { + return info.get_min() + (info.get_bin_width() * (i + 0.5)); + } + const Calibrator& get_calibration() const { return info; } + + const vector_type& pdf() const { return list; } + cdf_type cdf() const; + + default_type first_min () const; + + default_type entropy () const; + + protected: + const Calibrator info; + vector_type list; + friend class Kernel; + }; + + + // Convenience functions for calibrating (& histograming) basic input images + template + void calibrate (Calibrator& result, ImageType& image) + { + for (auto l = Loop(image) (image); l; ++l) + result (image.value()); + result.finalize (image.ndim() > 3 ? image.size(3) : 1); + } + + template + void calibrate (Calibrator& result, ImageType& image, MaskType& mask) + { + if (!mask.valid()) { + calibrate (result, image); + return; + } + if (!dimensions_match (image, mask)) + throw Exception ("Image and mask for histogram generation do not match"); + for (auto l = Loop(image) (image, mask); l; ++l) { + if (mask.value()) + result (image.value()); + } + result.finalize (image.ndim() > 3 ? image.size(3) : 1); + } + + template + Data generate (ImageType& image, const size_t num_bins, const bool ignore_zero = false) + { + Calibrator calibrator (num_bins, ignore_zero); + calibrate (calibrator, image); + return generate (calibrator, image); + } + + template + Data generate (ImageType& image, MaskType& mask, const size_t num_bins, const bool ignore_zero = false) + { + Calibrator calibrator (num_bins, ignore_zero); + calibrate (calibrator, image, mask); + return generate (calibrator, image, mask); + } + + template + Data generate (const Calibrator& calibrator, ImageType& image) + { + Data result (calibrator); + for (auto l = Loop(image) (image); l; ++l) + result (typename ImageType::value_type (image.value())); + return result; + } + + template + Data generate (const Calibrator& calibrator, ImageType& image, MaskType& mask) + { + if (!mask.valid()) + return generate (calibrator, image); + Data result (calibrator); + for (auto l = Loop(image) (image, mask); l; ++l) { + if (mask.value()) + result (typename ImageType::value_type (image.value())); } + return result; + } + + + + + class Matcher + { + + typedef Eigen::Array vector_type; + + public: + Matcher (const Data& input, const Data& target); + + default_type operator() (const default_type) const; + + private: + const Calibrator calib_input, calib_target; + vector_type mapping; + + }; + - protected: - class Entry - { - public: - Entry () : frequency (0), value (0.0) { } - size_t frequency; - value_type value; - }; - std::vector list; - friend class Kernel; - }; + } + } } #endif diff --git a/lib/stats.cpp b/lib/stats.cpp index 0b91e080f1..6f08f6ece7 100644 --- a/lib/stats.cpp +++ b/lib/stats.cpp @@ -34,15 +34,6 @@ namespace MR "only perform computation within the specified binary mask image.") + Argument ("image").type_image_in () - + Option ("histogram", - "generate histogram of intensities and store in specified text file. Note " - "that the first line of the histogram gives the centre of the bins.") - + Argument ("file").type_file_out () - - + Option ("bins", - "the number of bins to use to generate the histogram (default = " + str(DEFAULT_HISTOGRAM_BINS) + ").") - + Argument ("num").type_integer (2) - + Option ("dump", "dump the voxel intensities to a text file.") + Argument ("file").type_file_out (); diff --git a/lib/stats.h b/lib/stats.h index 31ed262d0e..af72a7a672 100644 --- a/lib/stats.h +++ b/lib/stats.h @@ -17,12 +17,11 @@ #include "app.h" +#include "algo/histogram.h" +#include "file/ofstream.h" #include "math/median.h" -#define DEFAULT_HISTOGRAM_BINS 100 - - namespace MR { @@ -36,62 +35,44 @@ namespace MR typedef cfloat complex_type; - class CalibrateHistogram - { - public: - CalibrateHistogram (int nbins) : min (std::numeric_limits::infinity()), max (-std::numeric_limits::infinity()), width (0.0), bins (nbins) { } - - value_type min, max, width; - int bins; - - void operator() (value_type val) { - if (std::isfinite (val)) { - if (val < min) min = val; - if (val > max) max = val; - } - } - - void init (std::ostream& stream) { - width = (max - min) / float (bins+1); - for (int i = 0; i < bins; i++) - stream << (min + width/2.0) + i* width << " "; - stream << "\n"; - } - }; - - - class Stats { public: - Stats (bool is_complex = false) : - mean (0.0, 0.0), - std (0.0, 0.0), - min (INFINITY, INFINITY), - max (-INFINITY, -INFINITY), - count (0), - dump (NULL), - is_complex (is_complex) { } - - void generate_histogram (const CalibrateHistogram& cal) { - hmin = cal.min; - hwidth = cal.width; - hist.resize (cal.bins); + Stats (const bool is_complex = false, const bool ignorezero = false) : + mean (0.0, 0.0), + std (0.0, 0.0), + min (INFINITY, INFINITY), + max (-INFINITY, -INFINITY), + count (0), + dump (NULL), + is_complex (is_complex), + ignore_zero (ignorezero) { } + + void generate_histogram (const Algo::Histogram::Calibrator& cal) { + hist.reset (new Algo::Histogram::Data (cal)); } void dump_to (std::ostream& stream) { dump = &stream; } - void write_histogram (std::ostream& stream) { - for (size_t i = 0; i < hist.size(); ++i) - stream << hist[i] << " "; + void write_histogram_header (std::ofstream& stream) const { + assert (hist); + for (size_t i = 0; i != hist->size(); ++i) + stream << hist->get_bin_centre(i) << " "; + stream << "\n"; + } + + void write_histogram_data (std::ofstream& stream) const { + assert (hist); + for (size_t i = 0; i < hist->size(); ++i) + stream << (*hist)[i] << " "; stream << "\n"; } void operator() (complex_type val) { - if (std::isfinite (val.real()) && std::isfinite (val.imag())) { + if (std::isfinite (val.real()) && std::isfinite (val.imag()) && !(ignore_zero && val.real() == 0.0 && val.imag() == 0.0)) { mean += val; std += cdouble (val.real()*val.real(), val.imag()*val.imag()); if (min.real() > val.real()) min = complex_type (val.real(), min.imag()); @@ -106,15 +87,8 @@ namespace MR if (!is_complex) values.push_back(val.real()); - - if (hist.size()) { - int bin = int ( (val.real()-hmin) / hwidth); - if (bin < 0) - bin = 0; - else if (bin >= int (hist.size())) - bin = hist.size()-1; - hist[bin]++; - } + if (hist) + (*hist) (val.real()); } } @@ -172,10 +146,9 @@ namespace MR cdouble mean, std; complex_type min, max; size_t count; - value_type hmin, hwidth; - std::vector hist; + std::unique_ptr hist; std::ostream* dump; - bool is_complex; + const bool is_complex, ignore_zero; std::vector values; }; From dc0500efc3ed732cef9fc2210d05e1263f36f518 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sat, 23 Jul 2016 22:06:34 +1000 Subject: [PATCH 035/723] Stats: Reduce field width to fit printout inside default 80-character terminal --- lib/stats.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/stats.h b/lib/stats.h index af72a7a672..44f5c07970 100644 --- a/lib/stats.h +++ b/lib/stats.h @@ -124,10 +124,10 @@ namespace MR s += str (ima.index(n)) + " "; else s += "0 "; - s += "] "; + s += "]"; - int width = is_complex ? 24 : 12; - std::cout << std::setw(15) << std::right << s << " "; + int width = is_complex ? 20 : 10; + std::cout << std::setw(12) << std::right << s << " "; std::cout << std::setw(width) << std::right << ( count ? str(mean) : "N/A" ); @@ -137,7 +137,7 @@ namespace MR std::cout << " " << std::setw(width) << std::right << ( count > 1 ? str(std) : "N/A" ) << " " << std::setw(width) << std::right << ( count ? str(min) : "N/A" ) << " " << std::setw(width) << std::right << ( count ? str(max) : "N/A" ) - << " " << std::setw(12) << std::right << count << "\n"; + << " " << std::setw(10) << std::right << count << "\n"; } } @@ -156,15 +156,15 @@ namespace MR inline void print_header (bool is_complex) { - int width = is_complex ? 24 : 12; - std::cout << std::setw(15) << std::right << "volume" + int width = is_complex ? 20 : 10; + std::cout << std::setw(12) << std::right << "volume" << " " << std::setw(width) << std::right << "mean"; if (!is_complex) std::cout << " " << std::setw(width) << std::right << "median"; - std::cout << " " << std::setw(width) << std::right << "std. dev." + std::cout << " " << std::setw(width) << std::right << "stdev" << " " << std::setw(width) << std::right << "min" << " " << std::setw(width) << std::right << "max" - << " " << std::setw(12) << std::right << "count\n"; + << " " << std::setw(10) << std::right << "count" << "\n"; } } From c84e45cd4c68a2b3e795433edb0c8cd58d4dfbcd Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 26 Jul 2016 00:27:00 +1000 Subject: [PATCH 036/723] Further changes to stats and histogram code Following from discussion in #671. - Remove histogram generation functionality from mrstats and fixelstats; these are instead handled using new commands mrhistogram and fixelhistogram. - Removed some historical capabilities from mrstats that were limiting code expansion. - New option for mrstats to generate statistics across all volumes, rather than the default (separate statistics for each volume). - Histograms should now detect when the input data consists of integers, and set the bin width to be an integer accordingly. Closes #71. --- cmd/fixelhistogram.cpp | 102 ++++++++++++ cmd/fixelstats.cpp | 59 +------ cmd/mrhistmatch.cpp | 5 +- cmd/mrhistogram.cpp | 157 ++++++++++++++++++ cmd/mrstats.cpp | 182 ++++----------------- cmd/mrthreshold.cpp | 29 +--- docs/reference/commands/fixelhistogram.rst | 64 ++++++++ docs/reference/commands/fixelstats.rst | 9 +- docs/reference/commands/mrhistogram.rst | 70 ++++++++ docs/reference/commands/mrstats.rst | 15 +- docs/reference/commands/mrthreshold.rst | 6 +- docs/reference/commands_list.rst | 4 + lib/algo/histogram.cpp | 30 ++-- lib/algo/histogram.h | 6 +- lib/stats.cpp | 5 +- lib/stats.h | 37 +---- testing/data | 2 +- testing/tests/mrstats | 4 +- testing/tests/mrthreshold | 1 - 19 files changed, 473 insertions(+), 314 deletions(-) create mode 100644 cmd/fixelhistogram.cpp create mode 100644 cmd/mrhistogram.cpp create mode 100644 docs/reference/commands/fixelhistogram.rst create mode 100644 docs/reference/commands/mrhistogram.rst diff --git a/cmd/fixelhistogram.cpp b/cmd/fixelhistogram.cpp new file mode 100644 index 0000000000..fd2dcf4c84 --- /dev/null +++ b/cmd/fixelhistogram.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + +#include "command.h" +#include "algo/histogram.h" +#include "algo/loop.h" + +#include "image.h" + +#include "sparse/fixel_metric.h" +#include "sparse/keys.h" +#include "sparse/image.h" + + +using namespace MR; +using namespace App; + +using Sparse::FixelMetric; + +void usage () +{ + AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; + + DESCRIPTION + + "Generate a histogram of fixel values."; + + ARGUMENTS + + Argument ("input", "the input fixel image.").type_image_in (); + + OPTIONS + + Algo::Histogram::Options; +} + + +void run () +{ + Sparse::Image input (argument[0]); + + auto opt = get_options("mask"); + std::unique_ptr > mask_ptr; + if (opt.size()) { + mask_ptr.reset (new Sparse::Image (opt[0][0])); + check_dimensions (input, *mask_ptr); + } + + File::OFStream output (argument[1]); + + size_t nbins = get_option_value ("bins", 0); + Algo::Histogram::Calibrator calibrator (nbins, get_options ("ignorezero").size()); + for (auto i = Loop (input) (input); i; ++i) { + if (mask_ptr) { + assign_pos_of (input).to (*mask_ptr); + if (input.value().size() != mask_ptr->value().size()) + throw Exception ("the input fixel image and mask image to not have corrresponding fixels"); + } + for (size_t fixel = 0; fixel != input.value().size(); ++fixel) { + if (mask_ptr) { + if (mask_ptr->value()[fixel].value > 0.5) + calibrator (input.value()[fixel].value); + } else { + calibrator (input.value()[fixel].value); + } + } + } + calibrator.finalize (1, false); + + Algo::Histogram::Data histogram (calibrator); + + for (auto i = Loop (input) (input); i; ++i) { + if (mask_ptr) + assign_pos_of (input).to (*mask_ptr); + + for (size_t fixel = 0; fixel != input.value().size(); ++fixel) { + if (mask_ptr) { + if (mask_ptr->value()[fixel].value > 0.5) + histogram (input.value()[fixel].value); + } else { + histogram (input.value()[fixel].value); + } + } + } + + for (size_t i = 0; i != nbins; ++i) + output << (calibrator.get_min() + ((i+0.5) * calibrator.get_bin_width())) << ","; + output << "\n"; + for (size_t i = 0; i != nbins; ++i) + output << histogram[i] << ","; + output << "\n"; +} diff --git a/cmd/fixelstats.cpp b/cmd/fixelstats.cpp index 1284d8f5fd..1aae3e924f 100644 --- a/cmd/fixelstats.cpp +++ b/cmd/fixelstats.cpp @@ -15,8 +15,6 @@ #include "command.h" -#include "progressbar.h" -#include "algo/histogram.h" #include "algo/loop.h" #include "stats.h" @@ -43,8 +41,7 @@ void usage () + Argument ("input", "the input fixel image.").type_image_in (); OPTIONS - + Stats::Options - + Algo::Histogram::Options; + + Stats::Options; } @@ -59,55 +56,13 @@ void run () check_dimensions (input, *mask_ptr); } - std::unique_ptr dumpstream, position_stream; - - opt = get_options ("dump"); - if (opt.size()) - dumpstream.reset (new File::OFStream (opt[0][0])); - - opt = get_options ("position"); - if (opt.size()) - position_stream.reset (new File::OFStream (opt[0][0])); - std::vector fields; opt = get_options ("output"); for (size_t n = 0; n < opt.size(); ++n) fields.push_back (opt[n][0]); - bool header_shown (!App::log_level || fields.size()); - Stats::Stats stats (false); - if (dumpstream) - stats.dump_to (*dumpstream); - - opt = get_options ("histogram"); - std::string histogram_path; - if (opt.size()) { - histogram_path = std::string(opt[0][0]); - const size_t nbins = get_option_value ("bins", 0); - Algo::Histogram::Calibrator calibrate (nbins, false); - for (auto i = Loop (input) (input); i; ++i) { - if (mask_ptr) { - assign_pos_of (input).to (*mask_ptr); - if (input.value().size() != mask_ptr->value().size()) - throw Exception ("the input fixel image and mask image to not have corrresponding fixels"); - } - for (size_t fixel = 0; fixel != input.value().size(); ++fixel) { - if (mask_ptr) { - if (mask_ptr->value()[fixel].value > 0.5) - calibrate (input.value()[fixel].value); - } else { - calibrate (input.value()[fixel].value); - } - } - } - calibrate.finalize (1); - stats.generate_histogram (calibrate); - } else if (get_options ("bins").size()) { - WARN ("Option -bins ignored as -histogram was not specified"); - } - for (auto i = Loop (input) (input); i; ++i) { if (mask_ptr) { assign_pos_of (input).to (*mask_ptr); @@ -125,16 +80,6 @@ void run () } } - if (!header_shown) - Stats::print_header (false); - header_shown = true; - + Stats::print_header (false); stats.print (input, fields); - - if (histogram_path.size()) { - File::OFStream stream (histogram_path); - stats.write_histogram_header (stream); - stats.write_histogram_data (stream); - } - } diff --git a/cmd/mrhistmatch.cpp b/cmd/mrhistmatch.cpp index f1440bc783..1f79db9f69 100644 --- a/cmd/mrhistmatch.cpp +++ b/cmd/mrhistmatch.cpp @@ -80,10 +80,7 @@ void run () 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); - // Now need to find the conversion function to map the input histogram to the target one - - // TODO Move this to a header somewhere - + // Non-linear intensity mapping determined within this class Algo::Histogram::Matcher matcher (hist_input, hist_target); // Generate the output image diff --git a/cmd/mrhistogram.cpp b/cmd/mrhistogram.cpp new file mode 100644 index 0000000000..5a78846cc2 --- /dev/null +++ b/cmd/mrhistogram.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + +#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)"; + +DESCRIPTION + + "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(); + +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; + } + + 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; +}; + + + +template +void run_volume (Functor& functor, Image& data, Image& mask) +{ + if (mask.valid()) { + for (auto l = Loop(0,3) (data, mask); l; ++l) { + if (mask.value()) + functor (float(data.value())); + } + } else { + for (auto l = Loop(0,3) (data); l; ++l) + functor (float(data.value())); + } +} + + + +void run () +{ + + auto header = Header::open (argument[0]); + if (header.ndim() > 4) + 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"); + auto data = header.get_image(); + + const bool allvolumes = get_options ("allvolumes").size(); + size_t nbins = get_option_value ("bins", 0); + + auto opt = get_options ("mask"); + Image mask; + if (opt.size()) { + mask = Image::open (opt[0][0]); + check_dimensions (mask, header, 0, 3); + } + + File::OFStream output (argument[1]); + + Algo::Histogram::Calibrator calibrator (nbins, get_options ("ignorezero").size()); + for (auto v = Volume_loop(data); v; ++v) + 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); + nbins = calibrator.get_num_bins(); + + for (size_t i = 0; i != nbins; ++i) + output << (calibrator.get_min() + ((i+0.5) * calibrator.get_bin_width())) << ","; + output << "\n"; + + if (allvolumes) { + + Algo::Histogram::Data histogram (calibrator); + for (auto v = Volume_loop(data); v; ++v) + run_volume (histogram, data, mask); + for (size_t i = 0; i != nbins; ++i) + output << histogram[i] << ","; + output << "\n"; + + } else { + + for (auto v = Volume_loop(data); v; ++v) { + 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/mrstats.cpp b/cmd/mrstats.cpp index 4c22b5c2b7..566a22fb1c 100644 --- a/cmd/mrstats.cpp +++ b/cmd/mrstats.cpp @@ -47,21 +47,9 @@ ARGUMENTS OPTIONS + Stats::Options - + Algo::Histogram::Options - + OptionGroup ("Additional options for mrstats") - - + Option ("ignorezero", "ignore zero-valued input voxels.") - - + Option ("voxel", - "only perform computation within the specified voxel(s), supplied as a " - "comma-separated vector of 3 integer values (multiple voxels can be included).") - .allow_multiple() - + Argument ("pos").type_sequence_int () - - + Option ("position", - "dump the position of the voxels in the mask to a text file.") - + Argument ("file").type_file_out (); + + Option ("allvolumes", "generate statistics across all image volumes, rather than one set of statistics per image volume") + + Option ("ignorezero", "ignore zero-valued input voxels."); } @@ -69,6 +57,7 @@ OPTIONS typedef Stats::value_type value_type; typedef Stats::complex_type complex_type; + class Volume_loop { public: @@ -107,160 +96,61 @@ class Volume_loop +void run_volume (Stats::Stats& stats, Image& data, Image& mask) +{ + if (mask.valid()) { + for (auto l = Loop(0,3) (data, mask); l; ++l) { + if (mask.value()) + stats (data.value()); + } + } else { + for (auto l = Loop(0,3) (data); l; ++l) + stats (data.value()); + } +} + + + + void run () { auto header = Header::open (argument[0]); + if (header.ndim() > 4) + 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(); - if (data.ndim() > 4) - throw Exception ("mrstats is not designed to handle images greater than 4D"); const bool ignorezero = get_options("ignorezero").size(); - auto inner_loop = Loop(0, 3); - - std::unique_ptr dumpstream, hist_stream, position_stream; - - auto opt = get_options ("voxel"); - std::vector voxels; - if (opt.size()) { - voxels.resize (opt.size()); - for (size_t i = 0; i < opt.size(); ++i) { - std::vector x = parse_ints (opt[i][0]); - if (x.size() != 3) - throw Exception ("vector positions must be supplied as x,y,z"); - if (x[0] < 0 || x[0] >= data.size (0) || - x[1] < 0 || x[1] >= data.size (1) || - x[2] < 0 || x[2] >= data.size (2)) - throw Exception ("voxel at [ " + str (x[0]) + " " + str (x[1]) - + " " + str (x[2]) + " ] is out of bounds"); - voxels[i] = { x[0], x[1], x[2] }; - } - } - - opt = get_options ("mask"); + auto opt = get_options ("mask"); Image mask; if (opt.size()) { - if (voxels.size()) - throw Exception ("cannot use mask with -voxel option"); mask = Image::open (opt[0][0]); - check_dimensions (mask, data, 0, 3); - } - - std::unique_ptr calibrate; - opt = get_options ("histogram"); - if (opt.size()) { - if (is_complex) - throw Exception ("histogram generation not supported for complex data types"); - hist_stream.reset (new File::OFStream (opt[0][0])); - const size_t nbins = get_option_value ("bins", 0); - calibrate.reset (new Algo::Histogram::Calibrator (nbins, ignorezero)); - ProgressBar progress ("Calibrating histogram", voxels.size() ? (voxels.size() * (data.ndim() > 3 ? data.size(3) : 1)) : voxel_count (data)); - for (auto i = Volume_loop (data); i; ++i) { - if (mask.valid()) { - for (auto j = inner_loop (mask, data); j; ++j) { - if (mask.value()) - (*calibrate) (complex_type(data.value()).real()); - ++progress; - } - } else if (voxels.size()) { - for (size_t i = 0; i < voxels.size(); ++i) { - data.index(0) = voxels[i][0]; - data.index(1) = voxels[i][1]; - data.index(2) = voxels[i][2]; - (*calibrate) (complex_type(data.value()).real()); - } - } else { - for (auto j = inner_loop (data); j; ++j) - (*calibrate) (complex_type(data.value()).real()); - ++progress; - } - } - calibrate->finalize (data.ndim() > 3 ? data.size(3) : 1); - } else if (get_options("bins").size()) { - WARN ("Option -bins ignored as -histogram was not specified"); + check_dimensions (mask, header, 0, 3); } - opt = get_options ("dump"); - if (opt.size()) - dumpstream.reset (new File::OFStream (opt[0][0])); - - opt = get_options ("position"); - if (opt.size()) - position_stream.reset (new File::OFStream (opt[0][0])); - std::vector fields; opt = get_options ("output"); for (size_t n = 0; n < opt.size(); ++n) fields.push_back (opt[n][0]); - bool header_shown (!App::log_level || fields.size()); - - bool first_volume = true; - for (auto i = Volume_loop (data); i; ++i) { - Stats::Stats stats (is_complex); - - if (dumpstream) - stats.dump_to (*dumpstream); - - if (hist_stream) { - stats.generate_histogram (*calibrate); - if (first_volume) - stats.write_histogram_header (*hist_stream); - } - - if (mask.valid()) { - - for (auto j = inner_loop (mask, data); j; ++j) { - if (mask.value() > 0.5) { - stats (data.value()); - if (position_stream) { - for (size_t i = 0; i < data.ndim(); ++i) - *position_stream << data.index(i) << " "; - *position_stream << "\n"; - } - } - } - - } else if (voxels.size()) { - - for (size_t i = 0; i < voxels.size(); ++i) { - data.index(0) = voxels[i][0]; - data.index(1) = voxels[i][1]; - data.index(2) = voxels[i][2]; - stats (data.value()); - if (position_stream) { - for (size_t i = 0; i < data.ndim(); ++i) - *position_stream << data.index(i) << " "; - *position_stream << "\n"; - } - } + if (App::log_level && fields.empty()) + Stats::print_header (is_complex); - } else { - - for (auto j = inner_loop (data); j; ++j) { - stats (data.value()); - if (position_stream) { - for (size_t i = 0; i < data.ndim(); ++i) - *position_stream << data.index(i) << " "; - *position_stream << "\n"; - } - } - - } - - if (!header_shown) - Stats::print_header (is_complex); - header_shown = true; + 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); - if (hist_stream) - stats.write_histogram_data (*hist_stream); - - first_volume = false; + } else { - } // End looping over volumes + 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 deb1132043..75d0dc01e8 100644 --- a/cmd/mrthreshold.cpp +++ b/cmd/mrthreshold.cpp @@ -24,7 +24,6 @@ #include "progressbar.h" #include "algo/loop.h" -#include "algo/histogram.h" #include "filter/optimal_threshold.h" @@ -36,13 +35,12 @@ void usage () AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; DESCRIPTION - + "create bitwise image by thresholding image intensity. By default, an " + + "Create bitwise image by thresholding image intensity. By default, an " "optimal threshold is determined using a parameter-free method. " - "Alternatively the threshold can be defined manually by the user " - "or using a histogram-based analysis to cut out the background."; + "Alternatively the threshold can be defined manually by the user."; REFERENCES - + "* If not using the -histogram option or any manual thresholding option:\n" + + "* If not using any manual thresholding option:\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"; @@ -56,9 +54,6 @@ void usage () + Option ("abs", "specify threshold value as absolute intensity.") + Argument ("value").type_float() - + Option ("histogram", "define the threshold by a histogram analysis to cut out the background. " - "Note that only the first study is used for thresholding.") - + Option ("percentile", "threshold the image at the ith percentile.") + Argument ("value").type_float (0.0, 100.0) @@ -88,7 +83,6 @@ void usage () void run () { default_type threshold_value (NaN), percentile (NaN), bottomNpercent (NaN), topNpercent (NaN); - bool use_histogram = false; size_t topN (0), bottomN (0), nopt (0); auto opt = get_options ("abs"); @@ -97,12 +91,6 @@ void run () ++nopt; } - opt = get_options ("histogram"); - if (opt.size()) { - use_histogram = true; - ++nopt; - } - opt = get_options ("percentile"); if (opt.size()) { percentile = opt[0][0]; @@ -234,17 +222,8 @@ void run () opt = get_options ("mask"); if (opt.size()) mask = Image::open (opt[0][0]); - if (use_histogram) { - if (mask.valid()) { - auto hist = Algo::Histogram::generate (in, mask, 0); - threshold_value = hist.first_min(); - } else { - auto hist = Algo::Histogram::generate (in, 0); - threshold_value = hist.first_min(); - } - } else if (std::isnan (threshold_value)) { + if (std::isnan (threshold_value)) threshold_value = Filter::estimate_optimal_threshold (in, mask); - } const std::string msg = "thresholding \"" + shorten (in.name()) + "\" at intensity " + str (threshold_value); for (auto l = Loop(msg, in) (in, out); l; ++l) { diff --git a/docs/reference/commands/fixelhistogram.rst b/docs/reference/commands/fixelhistogram.rst new file mode 100644 index 0000000000..05696b7b40 --- /dev/null +++ b/docs/reference/commands/fixelhistogram.rst @@ -0,0 +1,64 @@ +.. _fixelhistogram: + +fixelhistogram +=========== + +Synopsis +-------- + +:: + + fixelhistogram [ options ] input + +- *input*: the input fixel image. + +Description +----------- + +Generate a histogram of fixel values. + +Options +------- + +Histogram generation options +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-bins num** Manually set the number of bins to use to generate the histogram. + +- **-mask image** Calculate the histogram only within a mask image. + +- **-ignorezero** ignore zero-valued data during histogram construction. + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) + +- **-failonwarn** terminate program if a warning is produced + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +-------------- + + + +**Author:** Robert E. Smith (robert.smith@florey.edu.au) + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + diff --git a/docs/reference/commands/fixelstats.rst b/docs/reference/commands/fixelstats.rst index df9fccad96..7a5cf0e777 100644 --- a/docs/reference/commands/fixelstats.rst +++ b/docs/reference/commands/fixelstats.rst @@ -27,14 +27,7 @@ Statistics options - **-mask image** only perform computation within the specified binary mask image. -- **-dump file** dump the voxel intensities to a text file. - -Histogram generation options -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -- **-histogram file** generate histogram of intensities and store in specified text file. Note that the first line of the histogram gives the centre of the bins. - -- **-bins num** Manually set the number of bins to use to generate the histogram. +- **-ignorezero** ignore zero values during statistics calculation Standard options ^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/mrhistogram.rst b/docs/reference/commands/mrhistogram.rst new file mode 100644 index 0000000000..ebffc404c5 --- /dev/null +++ b/docs/reference/commands/mrhistogram.rst @@ -0,0 +1,70 @@ +.. _mrhistogram: + +mrhistogram +=========== + +Synopsis +-------- + +:: + + mrhistogram [ options ] image hist + +- *image*: the input image from which the histogram will be computed +- *hist*: the output histogram file + +Description +----------- + +generate a histogram of image intensities. + +Options +------- + +Histogram generation options +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-bins num** Manually set the number of bins to use to generate the histogram. + +- **-mask image** Calculate the histogram only within a mask image. + +- **-ignorezero** ignore zero-valued data during histogram construction. + +Additional options for mrhistogram +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-allvolumes** generate one histogram across all image volumes, rather than one per image volume + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) + +- **-failonwarn** terminate program if a warning is produced + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +-------------- + + + +**Author:** Robert E. Smith (robert.smith@florey.edu.au) + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + diff --git a/docs/reference/commands/mrstats.rst b/docs/reference/commands/mrstats.rst index f1b4d4e6d9..5bdc459bc2 100644 --- a/docs/reference/commands/mrstats.rst +++ b/docs/reference/commands/mrstats.rst @@ -27,23 +27,14 @@ Statistics options - **-mask image** only perform computation within the specified binary mask image. -- **-dump file** dump the voxel intensities to a text file. - -Histogram generation options -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -- **-histogram file** generate histogram of intensities and store in specified text file. Note that the first line of the histogram gives the centre of the bins. - -- **-bins num** Manually set the number of bins to use to generate the histogram. +- **-ignorezero** ignore zero values during statistics calculation Additional options for mrstats ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- **-ignorezero** ignore zero-valued input voxels. - -- **-voxel pos** only perform computation within the specified voxel(s), supplied as a comma-separated vector of 3 integer values (multiple voxels can be included). +- **-allvolumes** generate statistics across all image volumes, rather than one set of statistics per image volume -- **-position file** dump the position of the voxels in the mask to a text file. +- **-ignorezero** ignore zero-valued input voxels. Standard options ^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/mrthreshold.rst b/docs/reference/commands/mrthreshold.rst index e4011fe442..10ca10735b 100644 --- a/docs/reference/commands/mrthreshold.rst +++ b/docs/reference/commands/mrthreshold.rst @@ -16,15 +16,13 @@ Synopsis Description ----------- -create bitwise image by thresholding image intensity. By default, an optimal threshold is determined using a parameter-free method. Alternatively the threshold can be defined manually by the user or using a histogram-based analysis to cut out the background. +Create bitwise image by thresholding image intensity. By default, an optimal threshold is determined using a parameter-free method. Alternatively the threshold can be defined manually by the user. Options ------- - **-abs value** specify threshold value as absolute intensity. -- **-histogram** define the threshold by a histogram analysis to cut out the background. Note that only the first study is used for thresholding. - - **-percentile value** threshold the image at the ith percentile. - **-top N** provide a mask of the N top-valued voxels @@ -65,7 +63,7 @@ Standard options References ^^^^^^^^^^ -* If not using the -histogram option or any manual thresholding option: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 manual thresholding option: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 -------------- diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index b13af298d3..be6cb911bf 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -65,6 +65,8 @@ List of MRtrix3 commands commands/fixelcorrespondence + commands/fixelhistogram + commands/fixellog commands/fixelreorient @@ -109,6 +111,8 @@ List of MRtrix3 commands commands/mrhistmatch + commands/mrhistogram + commands/mrinfo commands/mrmath diff --git a/lib/algo/histogram.cpp b/lib/algo/histogram.cpp index 65a49c841e..ad47678407 100644 --- a/lib/algo/histogram.cpp +++ b/lib/algo/histogram.cpp @@ -27,19 +27,18 @@ namespace MR const OptionGroup Options = OptionGroup ("Histogram generation options") - + Option ("histogram", - "generate histogram of intensities and store in specified text file. Note " - "that the first line of the histogram gives the centre of the bins.") - + Argument ("file").type_file_out () + + Option ("bins", "Manually set the number of bins to use to generate the histogram.") + + Argument ("num").type_integer (2) - + Option ("bins", - "Manually set the number of bins to use to generate the histogram.") - + Argument ("num").type_integer (2); + + Option ("mask", "Calculate the histogram only within a mask image.") + + Argument ("image").type_image_in() + + Option ("ignorezero", "ignore zero-valued data during histogram construction."); - void Calibrator::finalize (const size_t num_volumes) + + void Calibrator::finalize (const size_t num_volumes, const bool is_integer) { if (!std::isfinite (bin_width)) { if (num_bins) { @@ -52,10 +51,17 @@ namespace MR // Will need to revisit if mrstats gets capability to compute statistics across all volumes rather than splitting bin_width = 2.0 * get_iqr() * std::pow (data.size() / num_volumes, -1.0/3.0); std::vector().swap (data); // No longer required; free the memory used - // Now set the number of bins, and recalculate the bin width, to ensure - // evenly-spaced bins from min to max - num_bins = std::round ((max - min) / get_bin_width()); - bin_width = (max - min) / default_type(num_bins); + // If the input data are integers, the bin width should also be an integer, to avoid getting + // regular spike artifacts in the histogram + if (is_integer) { + bin_width = std::round (bin_width); + num_bins = std::ceil ((max - min) / bin_width); + } else { + // Now set the number of bins, and recalculate the bin width, to ensure + // evenly-spaced bins from min to max + num_bins = std::round ((max - min) / get_bin_width()); + bin_width = (max - min) / default_type(num_bins); + } } } } diff --git a/lib/algo/histogram.h b/lib/algo/histogram.h index cbd5fe1065..28554ef73d 100644 --- a/lib/algo/histogram.h +++ b/lib/algo/histogram.h @@ -54,7 +54,7 @@ namespace MR return true; } - void finalize (const size_t num_volumes); + void finalize (const size_t num_volumes, const bool is_integer); default_type get_bin_width() const { return bin_width; } size_t get_num_bins() const { return num_bins; } @@ -138,7 +138,7 @@ namespace MR { for (auto l = Loop(image) (image); l; ++l) result (image.value()); - result.finalize (image.ndim() > 3 ? image.size(3) : 1); + result.finalize (image.ndim() > 3 ? image.size(3) : 1, std::is_integral::value); } template @@ -154,7 +154,7 @@ namespace MR if (mask.value()) result (image.value()); } - result.finalize (image.ndim() > 3 ? image.size(3) : 1); + result.finalize (image.ndim() > 3 ? image.size(3) : 1, std::is_integral::value); } template diff --git a/lib/stats.cpp b/lib/stats.cpp index 6f08f6ece7..20dcb140b2 100644 --- a/lib/stats.cpp +++ b/lib/stats.cpp @@ -34,9 +34,8 @@ namespace MR "only perform computation within the specified binary mask image.") + Argument ("image").type_image_in () - + Option ("dump", - "dump the voxel intensities to a text file.") - + Argument ("file").type_file_out (); + + Option ("ignorezero", + "ignore zero values during statistics calculation"); } diff --git a/lib/stats.h b/lib/stats.h index 44f5c07970..0afafef85b 100644 --- a/lib/stats.h +++ b/lib/stats.h @@ -17,7 +17,6 @@ #include "app.h" -#include "algo/histogram.h" #include "file/ofstream.h" #include "math/median.h" @@ -31,8 +30,8 @@ namespace MR extern const char * field_choices[]; extern const App::OptionGroup Options; - typedef float value_type; - typedef cfloat complex_type; + typedef default_type value_type; + typedef cdouble complex_type; class Stats @@ -44,32 +43,9 @@ namespace MR min (INFINITY, INFINITY), max (-INFINITY, -INFINITY), count (0), - dump (NULL), is_complex (is_complex), ignore_zero (ignorezero) { } - void generate_histogram (const Algo::Histogram::Calibrator& cal) { - hist.reset (new Algo::Histogram::Data (cal)); - } - - void dump_to (std::ostream& stream) { - dump = &stream; - } - - void write_histogram_header (std::ofstream& stream) const { - assert (hist); - for (size_t i = 0; i != hist->size(); ++i) - stream << hist->get_bin_centre(i) << " "; - stream << "\n"; - } - - void write_histogram_data (std::ofstream& stream) const { - assert (hist); - for (size_t i = 0; i < hist->size(); ++i) - stream << (*hist)[i] << " "; - stream << "\n"; - } - void operator() (complex_type val) { if (std::isfinite (val.real()) && std::isfinite (val.imag()) && !(ignore_zero && val.real() == 0.0 && val.imag() == 0.0)) { @@ -80,15 +56,8 @@ namespace MR if (max.real() < val.real()) max = complex_type (val.real(), max.imag()); if (max.imag() < val.imag()) max = complex_type (max.real(), val.imag()); count++; - - if (dump) - *dump << str(val) << "\n"; - if (!is_complex) values.push_back(val.real()); - - if (hist) - (*hist) (val.real()); } } @@ -146,8 +115,6 @@ namespace MR cdouble mean, std; complex_type min, max; size_t count; - std::unique_ptr hist; - std::ostream* dump; const bool is_complex, ignore_zero; std::vector values; }; diff --git a/testing/data b/testing/data index 623ee7ce61..055af93a9e 160000 --- a/testing/data +++ b/testing/data @@ -1 +1 @@ -Subproject commit 623ee7ce61cbbcf8a4a2ab74a333045da2c9880c +Subproject commit 055af93a9eb20fcf8a1a032f59616e3c4e7d8674 diff --git a/testing/tests/mrstats b/testing/tests/mrstats index fe5f5da247..f1075cb5c0 100644 --- a/testing/tests/mrstats +++ b/testing/tests/mrstats @@ -1,5 +1,3 @@ mrstats dwi.mif -output mean -output median -output std -output min -output max -output count > tmp.txt && testing_diff_matrix tmp.txt mrstats/out.txt 0.001 mrstats dwi.mif -output mean -output median -output std -output min -output max -output count -mask mask.mif > tmp.txt && testing_diff_matrix tmp.txt mrstats/masked.txt 0.001 -mrstats dwi.mif -histogram tmp.txt -force && testing_diff_matrix tmp.txt mrstats/hist.txt 0.5 -mrstats dwi_mean.mif -dump tmp.txt -force && testing_diff_matrix tmp.txt mrstats/dump.txt 0.5 -mrstats dwi_mean.mif -mask mask.mif -position tmp.txt -force && testing_diff_matrix tmp.txt mrstats/position.txt 0.5 +mrstats dwi.mif -output mean -output median -output std -output min -output max -output count -allvolumes > tmp.txt && testing_diff_matrix tmp.txt mrstats/allvolumes.txt 0.001 \ No newline at end of file diff --git a/testing/tests/mrthreshold b/testing/tests/mrthreshold index d5b9fe71af..32cfa32000 100644 --- a/testing/tests/mrthreshold +++ b/testing/tests/mrthreshold @@ -1,7 +1,6 @@ mrthreshold dwi_mean.mif - | testing_diff_data - mrthreshold/default.mif 0.5 mrthreshold dwi_mean.mif - -invert | testing_diff_data - mrthreshold/invert.mif 0.5 mrthreshold dwi_mean.mif - -abs 100 | testing_diff_data - mrthreshold/abs100.mif 0.5 -mrthreshold dwi_mean.mif - -histogram | testing_diff_data - mrthreshold/hist.mif 0.5 mrthreshold dwi_mean.mif - -percentile 50 | testing_diff_data - mrthreshold/perc50.mif 0.5 mrthreshold dwi_mean.mif - -top 20 | testing_diff_data - mrthreshold/top20.mif 0.5 mrthreshold dwi_mean.mif - -bottom 20 | testing_diff_data - mrthreshold/bottom20.mif 0.5 From 282791b9646013565ea6140b832de6e51bb2f7ba Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 26 Jul 2016 15:12:57 +1000 Subject: [PATCH 037/723] New command: connectomestats This command performs statistical inference directly on connectome matrices, at the edge level. Statistical enhancement is provided using either the original NBS, or the new NBS-TFCE method, or no statistical enhancement can be performed. Closes #723. --- cmd/connectomestats.cpp | 344 ++++++++++++++++++ docs/reference/commands/connectomestats.rst | 84 +++++ docs/reference/commands_list.rst | 4 + src/connectome/enhance.cpp | 130 +++++++ src/connectome/enhance.h | 133 +++++++ src/connectome/mat2vec.cpp | 51 ++- src/connectome/mat2vec.h | 133 ++++--- src/gui/mrview/tool/connectome/connectome.cpp | 5 +- 8 files changed, 800 insertions(+), 84 deletions(-) create mode 100644 cmd/connectomestats.cpp create mode 100644 docs/reference/commands/connectomestats.rst create mode 100644 src/connectome/enhance.cpp create mode 100644 src/connectome/enhance.h diff --git a/cmd/connectomestats.cpp b/cmd/connectomestats.cpp new file mode 100644 index 0000000000..87e5076a18 --- /dev/null +++ b/cmd/connectomestats.cpp @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + + +#include + +#include "command.h" +#include "progressbar.h" + +#include "file/path.h" +#include "math/stats/glm.h" +#include "math/stats/permutation.h" +#include "math/stats/typedefs.h" + +#include "connectome/enhance.h" +#include "connectome/mat2vec.h" + +#include "stats/permtest.h" + + +using namespace MR; +using namespace App; + + +const char* algorithms[] = { "nbs", "nbs_tfce", "none", nullptr }; + + + +// TODO Eventually these will move to some kind of TFCE header +#define TFCE_DH_DEFAULT 0.1 +#define TFCE_E_DEFAULT 0.4 +#define TFCE_H_DEFAULT 3.0 + + +#define NPERMS_DEFAULT 5000 +#define NPERMS_NONSTATIONARITY_DEFAULT 5000 + + +void usage () +{ + AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; + + DESCRIPTION + + "Connectome group-wise statistics at the edge level using non-parametric permutation testing."; + + + ARGUMENTS + + 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 ("design", "the design matrix. Note that a column of 1's will need to be added for correlations.").type_file_in () + + + Argument ("contrast", "the contrast vector, specified as a single row of weights").type_file_in () + + + Argument ("output", "the filename prefix for all output.").type_text(); + + + OPTIONS + + + Option ("notest", "don't perform permutation testing and only output population statistics (effect size, stdev etc)") + + + Option ("nperms", "the number of permutations (default: " + str(NPERMS_DEFAULT) + ").") + + Argument ("num").type_integer (1) + + + Option ("threshold", "the t-statistic value to use in threshold-based clustering algorithms") + + Argument ("value").type_float (0.0) + + // TODO OptionGroup these, and provide a generic loader function + + Option ("tfce_dh", "the height increment used in the tfce integration (default: 0.1)") + + Argument ("value").type_float (1e-6) + + + Option ("tfce_e", "tfce extent exponent (default: 0.5)") + + Argument ("value").type_float (0.0) + + + Option ("tfce_h", "tfce height exponent (default: 2.0)") + + Argument ("value").type_float (0.0) + + + Option ("nonstationary", "do adjustment for non-stationarity") + + + Option ("nperms_nonstationary", "the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: " + str(NPERMS_NONSTATIONARITY_DEFAULT) + ")") + + Argument ("num").type_integer (1); + + + + 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 NBS-TFCE algorithm: \n" + "Vinokur, L.; Zalesky, A.; Raffelt, D.; Smith, R.E. & Connelly, A. A Novel Threshold-Free Network-Based Statistics Method: Demonstration using Simulated Pathology. \n" + "OHBM, 2015, 4144" + + + "* 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"; + +} + + + +using Math::Stats::matrix_type; +using Math::Stats::vector_type; + + + + +void load_tfce_parameters (MR::Connectome::Enhance::TFCEWrapper& 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 (E, H, dh); +} + + + +void run() +{ + + // Read filenames + std::vector filenames; + { + std::string folder = Path::dirname (argument[0]); + std::ifstream ifs (argument[0].c_str()); + std::string temp; + while (getline (ifs, temp)) { + std::string filename (Path::join (folder, temp)); + size_t p = filename.find_last_not_of(" \t"); + if (std::string::npos != p) + filename.erase(p+1); + if (filename.size()) { + if (!MR::Path::exists (filename)) + throw Exception ("Input connectome file not found: \"" + filename + "\""); + filenames.push_back (filename); + } + } + } + + const MR::Connectome::matrix_type example_connectome = load_matrix (filenames.front()); + if (example_connectome.rows() != example_connectome.cols()) + throw Exception ("Connectome of first subject is not square (" + str(example_connectome.rows()) + " x " + str(example_connectome.cols()) + ")"); + const MR::Connectome::node_t num_nodes = example_connectome.rows(); + + // 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 MR::Connectome::Enhance::TFCEWrapper (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 size_t num_perms = get_option_value ("nperms", NPERMS_DEFAULT); + const bool do_nonstationary_adjustment = get_options ("nonstationary").size(); + const size_t nperms_nonstationary = get_option_value ("nperms_nonstationarity", NPERMS_NONSTATIONARITY_DEFAULT); + + // Load design matrix + const matrix_type design = load_matrix (argument[2]); + if (size_t(design.rows()) != filenames.size()) + throw Exception ("number of subjects does not match number of rows in design matrix"); + + // Load contrast matrix + matrix_type contrast = load_matrix (argument[3]); + if (contrast.cols() > design.cols()) + throw Exception ("too many contrasts for design matrix"); + contrast.conservativeResize (contrast.rows(), design.cols()); + + const std::string output_prefix = argument[4]; + + // Load input data + // For compatibility with existing statistics code, symmetric matrix data is adjusted + // into vector form - one row per edge in the symmetric connectome. The Mat2Vec class + // deals with the re-ordering of matrix data into this form. + MR::Connectome::Mat2Vec mat2vec (num_nodes); + const size_t num_edges = mat2vec.vec_size(); + matrix_type data (num_edges, filenames.size()); + { + ProgressBar progress ("Loading input connectome data", filenames.size()); + for (size_t subject = 0; subject < filenames.size(); subject++) { + + const std::string& path (filenames[subject]); + MR::Connectome::matrix_type subject_data; + try { + subject_data = load_matrix (path); + } catch (Exception& e) { + throw Exception (e, "Error loading connectome data for subject #" + str(subject) + " (file \"" + path + "\""); + } + + try { + MR::Connectome::to_upper (subject_data); + if (size_t(subject_data.rows()) != num_nodes) + throw Exception ("Connectome matrix is not the correct size (" + str(subject_data.rows()) + ", should be " + str(num_nodes) + ")"); + } catch (Exception& e) { + throw Exception (e, "Connectome for subject #" + str(subject) + " (file \"" + path + "\") invalid"); + } + + for (size_t i = 0; i != num_edges; ++i) + data(i, subject) = subject_data (mat2vec(i).first, mat2vec(i).second); + + ++progress; + } + } + + { + ProgressBar progress ("outputting beta coefficients, effect size and standard deviation...", contrast.cols() + 3); + + const matrix_type betas = Math::Stats::GLM::solve_betas (data, design); + for (size_t i = 0; i < size_t(contrast.cols()); ++i) { + save_matrix (mat2vec.V2M (betas.col(i)), output_prefix + "_beta_" + str(i) + ".csv"); + ++progress; + } + + const matrix_type abs_effects = Math::Stats::GLM::abs_effect_size (data, design, contrast); + save_matrix (mat2vec.V2M (abs_effects.col(0)), output_prefix + "_abs_effect.csv"); + ++progress; + + const matrix_type std_effects = Math::Stats::GLM::std_effect_size (data, design, contrast); + matrix_type first_std_effect = mat2vec.V2M (std_effects.col (0)); + for (MR::Connectome::node_t i = 0; i != num_nodes; ++i) { + for (MR::Connectome::node_t j = 0; j != num_nodes; ++j) { + if (!std::isfinite (first_std_effect (i, j))) + first_std_effect (i, j) = 0.0; + } + } + save_matrix (first_std_effect, output_prefix + "_std_effect.csv"); + ++progress; + + const matrix_type stdevs = Math::Stats::GLM::stdev (data, design); + save_vector (stdevs.col(0), output_prefix + "_std_dev.csv"); + } + + Math::Stats::GLMTTest glm_ttest (data, design, contrast); + + vector_type empirical_statistic; + + // If performing non-stationarity adjustment we need to pre-compute the empirical statistic + if (do_nonstationary_adjustment) { + switch (int(argument[1])) { + case 0: { + MR::Connectome::Enhance::NBS* temp = dynamic_cast (enhancer.get()); + Stats::PermTest::precompute_empirical_stat (glm_ttest, *temp, nperms_nonstationary, empirical_statistic); + } + break; + case 1: { + MR::Connectome::Enhance::TFCEWrapper* temp = dynamic_cast (enhancer.get()); + Stats::PermTest::precompute_empirical_stat (glm_ttest, *temp, nperms_nonstationary, empirical_statistic); + } + break; + case 2: { + MR::Connectome::Enhance::PassThrough* temp = dynamic_cast (enhancer.get()); + Stats::PermTest::precompute_empirical_stat (glm_ttest, *temp, nperms_nonstationary, empirical_statistic); + } + break; + } + save_matrix (mat2vec.V2M (empirical_statistic), output_prefix + "_empirical.csv"); + } + + // Precompute default statistic and enhanced statistic + vector_type tvalue_output (num_edges); + vector_type enhanced_output (num_edges); + + Stats::PermTest::precompute_default_permutation (glm_ttest, *enhancer, empirical_statistic, enhanced_output, std::shared_ptr(), tvalue_output); + + save_matrix (mat2vec.V2M (tvalue_output), output_prefix + "_tvalue.csv"); + save_matrix (mat2vec.V2M (enhanced_output), output_prefix + "_enhanced.csv"); + + // Perform permutation testing + if (!get_options ("notest").size()) { + + // FIXME Getting NANs in the null distribution + // Check: was result of pre-nulled subject data + vector_type null_distribution (num_perms); + vector_type uncorrected_pvalues (num_edges); + + switch (int(argument[1])) { + + case 0: { + MR::Connectome::Enhance::NBS* temp = dynamic_cast (enhancer.get()); + Stats::PermTest::run_permutations (glm_ttest, *temp, num_perms, empirical_statistic, + enhanced_output, std::shared_ptr(), + null_distribution, std::shared_ptr(), + uncorrected_pvalues, std::shared_ptr()); + } + break; + case 1: { + MR::Connectome::Enhance::TFCEWrapper* temp = dynamic_cast (enhancer.get()); + Stats::PermTest::run_permutations (glm_ttest, *temp, num_perms, empirical_statistic, + enhanced_output, std::shared_ptr(), + null_distribution, std::shared_ptr(), + uncorrected_pvalues, std::shared_ptr()); + } break; + case 2: { + MR::Connectome::Enhance::PassThrough* temp = dynamic_cast (enhancer.get()); + Stats::PermTest::run_permutations (glm_ttest, *temp, num_perms, empirical_statistic, + enhanced_output, std::shared_ptr(), + null_distribution, std::shared_ptr(), + uncorrected_pvalues, std::shared_ptr()); + } + break; + } + + save_vector (null_distribution, output_prefix + "_null_dist.txt"); + vector_type pvalue_output (num_edges); + Math::Stats::Permutation::statistic2pvalue (null_distribution, enhanced_output, pvalue_output); + save_matrix (mat2vec.V2M (pvalue_output), output_prefix + "_fwe_pvalue.csv"); + save_matrix (mat2vec.V2M (uncorrected_pvalues), output_prefix + "_uncorrected_pvalue.csv"); + + } + +} diff --git a/docs/reference/commands/connectomestats.rst b/docs/reference/commands/connectomestats.rst new file mode 100644 index 0000000000..e479da71ee --- /dev/null +++ b/docs/reference/commands/connectomestats.rst @@ -0,0 +1,84 @@ +.. _connectomestats: + +connectomestats +=========== + +Synopsis +-------- + +:: + + connectomestats [ options ] input algorithm design contrast output + +- *input*: a text file listing the file names of the input connectomes +- *algorithm*: the algorithm to use in network-based clustering/enhancement. Options are: nbs, nbs_tfce, none +- *design*: the design matrix. Note that a column of 1's will need to be added for correlations. +- *contrast*: the contrast vector, specified as a single row of weights +- *output*: the filename prefix for all output. + +Description +----------- + +Connectome group-wise statistics at the edge level using non-parametric permutation testing. + +Options +------- + +- **-notest** don't perform permutation testing and only output population statistics (effect size, stdev etc) + +- **-nperms num** the number of permutations (default: 5000). + +- **-threshold value** the t-statistic value to use in threshold-based clustering algorithms + +- **-tfce_dh value** the height increment used in the tfce integration (default: 0.1) + +- **-tfce_e value** tfce extent exponent (default: 0.5) + +- **-tfce_h value** tfce height exponent (default: 2.0) + +- **-nonstationary** do adjustment for non-stationarity + +- **-nperms_nonstationary num** the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: 5000) + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) + +- **-failonwarn** terminate program if a warning is produced + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +References +^^^^^^^^^^ + +* If using the NBS algorithm: Zalesky, A.; Fornito, A. & Bullmore, E. T. Network-based statistic: Identifying differences in brain networks. NeuroImage, 2010, 53, 1197-1207 + +* If using the NBS-TFCE algorithm: 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 + +* If using the -nonstationary option: 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 + +-------------- + + + +**Author:** Robert E. Smith (robert.smith@florey.edu.au) + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index aa3beb4d11..edcc8e4bcc 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -27,6 +27,10 @@ List of MRtrix3 commands ....... +.. include:: commands/connectomestats.rst +....... + + .. include:: commands/dcmedit.rst ....... diff --git a/src/connectome/enhance.cpp b/src/connectome/enhance.cpp new file mode 100644 index 0000000000..52f01b6d52 --- /dev/null +++ b/src/connectome/enhance.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + + +#include "connectome/enhance.h" +#include "connectome/mat2vec.h" + +#include + +#include "progressbar.h" + + +namespace MR { + namespace Connectome { + namespace Enhance { + + + + value_type PassThrough::enhance (const value_type max_t, const vector_type& in, vector_type& out) const + { + out = in; + return max_t; + } + + + + value_type NBS::enhance_at_t (const vector_type& in, vector_type& out, const value_type T) const + { + out = vector_type::Zero (in.size()); + value_type max_value = value_type(0); + + for (ssize_t seed = 0; seed != in.size(); ++seed) { + if (std::isfinite (in[seed]) && in[seed] >= T && !out[seed]) { + + BitSet visited (in.size()); + visited[seed] = true; + std::vector to_expand (1, seed); + size_t cluster_size = 0; + + while (to_expand.size()) { + + const uint32_t index = to_expand.back(); + to_expand.pop_back(); + cluster_size++; + + for (std::vector::const_iterator i = (*adjacency)[index].begin(); i != (*adjacency)[index].end(); ++i) { + if (!visited[*i] && std::isfinite(in[*i]) && in[*i] >= T) { + visited[*i] = true; + to_expand.push_back (*i); + } + } + + } + + max_value = std::max (max_value, value_type(cluster_size)); + for (ssize_t i = 0; i != in.size(); ++i) + out[i] += (visited[i] ? 1.0 : 0.0) * cluster_size; + + } + } + + return max_value; + } + + + + void NBS::initialise (const node_t num_nodes) + { + const Mat2Vec mat2vec (num_nodes); + const size_t num_edges = mat2vec.vec_size(); + ProgressBar progress ("Pre-computing statistical correlation matrix...", num_edges); + adjacency.reset (new std::vector< std::vector > (num_edges, std::vector())); + for (node_t row = 0; row != num_nodes; ++row) { + for (node_t column = row; column != num_nodes; ++column) { + + const size_t index = mat2vec (row, column); + std::vector& vector = (*adjacency)[index]; + vector.reserve (2 * (num_nodes-1)); + // Should be able to expand from this edge to any other edge connected to either row or column + for (node_t r = 0; r != num_nodes; ++r) { + if (r != row) + vector.push_back (mat2vec (r, column)); + } + for (node_t c = 0; c != num_nodes; ++c) { + if (c != column) + vector.push_back (mat2vec (row, c)); + } + ++progress; + } + } + } + + + + value_type TFCEWrapper::enhance (const value_type max_t, const vector_type& in, vector_type& out) const + { + out = vector_type::Zero (in.size()); + for (value_type h = dh; (h-dh) < max_t; h += dh) { + vector_type temp; + const value_type max = (*enhancer) (in, temp, h); + if (max) { + const value_type h_multiplier = std::pow (h, H); + for (size_t index = 0; index != size_t(in.size()); ++index) + out[index] += (std::pow (temp[index], E) * h_multiplier); + } + } + return out.maxCoeff(); + } + + + + } + } +} + + + diff --git a/src/connectome/enhance.h b/src/connectome/enhance.h new file mode 100644 index 0000000000..f6fa67dbbf --- /dev/null +++ b/src/connectome/enhance.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + + +#ifndef __connectome_enhance_h__ +#define __connectome_enhance_h__ + +#include +#include +#include + +#include "bitset.h" + +#include "connectome/mat2vec.h" + + + +namespace MR { + namespace Connectome { + namespace Enhance { + + + + // TODO This should instead be handled using the new base enhancer class + // These changes must not have been pushed... + class Base + { + public: + Base() { } + Base (const Base& that) = default; + virtual ~Base() { } + + value_type operator() (const value_type max, const vector_type& in, vector_type& out) const { return enhance (max, in, out); } + virtual value_type operator() (const vector_type& in, vector_type& out, const value_type t) const { return enhance_at_t (in, out, t); } + + protected: + virtual value_type enhance (const value_type, const vector_type&, vector_type&) const { assert (0); return NaN; } + virtual value_type enhance_at_t (const vector_type&, vector_type&, const value_type) const { assert (0); return NaN; } + + }; + + + + // This should be possible to use for either edge or node inference + class PassThrough : public Base + { + public: + PassThrough() { } + ~PassThrough() { } + + private: + value_type enhance (const value_type, const vector_type&, vector_type&) const override; + + }; + + + + class NBS : public Base + { + public: + + NBS () = delete; + NBS (const node_t i) : threshold (0.0) { initialise (i); } + NBS (const node_t i, const value_type t) : threshold (t) { initialise (i); } + NBS (const NBS& that) = default; + virtual ~NBS() { } + + void set_threshold (const value_type t) { threshold = t; } + + protected: + std::shared_ptr< std::vector< std::vector > > adjacency; + value_type threshold; + + private: + void initialise (const node_t); + + value_type enhance (const value_type max_t, const vector_type& in, vector_type& out) const override { + return enhance_at_t (in, out, threshold); + } + + value_type enhance_at_t (const vector_type&, vector_type&, const value_type) const override; + + }; + + + + class TFCEWrapper : public Base + { + public: + TFCEWrapper (const std::shared_ptr base) : enhancer (base), E (NaN), H (NaN), dh (NaN) { } + TFCEWrapper (const TFCEWrapper& that) = default; + ~TFCEWrapper() { } + + // TODO Homogenise TFCE + void set_tfce_parameters (const value_type extent, const value_type height, const value_type d_height) + { + E = extent; + H = height; + dh = d_height; + } + + private: + std::shared_ptr enhancer; + value_type E, H, dh; + + value_type enhance (const value_type, const vector_type&, vector_type&) const override; + + }; + + + + + + } + } +} + + +#endif + diff --git a/src/connectome/mat2vec.cpp b/src/connectome/mat2vec.cpp index 08f63457f6..168a94032e 100644 --- a/src/connectome/mat2vec.cpp +++ b/src/connectome/mat2vec.cpp @@ -18,40 +18,37 @@ namespace MR { -namespace Connectome { - - - -Mat2Vec::Mat2Vec (const node_t i) : - size (i) -{ - lookup.assign (size, std::vector (size, 0)); - inv_lookup.reserve (size * (size+1) / 2); - size_t index = 0; - for (node_t row = 0; row != size; ++row) { - for (node_t column = row; column != size; ++column) { - lookup[row][column] = lookup[column][row] = index++; - inv_lookup.push_back (std::make_pair (row, column)); + namespace Connectome { + + + + Mat2Vec::Mat2Vec (const node_t i) : + dim (i) + { + lookup.assign (dim, std::vector (dim, 0)); + inv_lookup.reserve (dim* (dim+1) / 2); + size_t index = 0; + for (node_t row = 0; row != dim; ++row) { + for (node_t column = row; column != dim; ++column) { + lookup[row][column] = lookup[column][row] = index++; + inv_lookup.push_back (std::make_pair (row, column)); + } + } } - } -} - - - -Mat2Vec& Mat2Vec::operator= (Mat2Vec&& that) -{ - size = that.size; that.size = 0; - lookup = std::move (that.lookup); - inv_lookup = std::move (that.inv_lookup); - return *this; -} + Mat2Vec& Mat2Vec::operator= (Mat2Vec&& that) + { + dim = that.dim; that.dim = 0; + lookup = std::move (that.lookup); + inv_lookup = std::move (that.inv_lookup); + return *this; + } -} + } } diff --git a/src/connectome/mat2vec.h b/src/connectome/mat2vec.h index 9ee75addcb..b97cdb254a 100644 --- a/src/connectome/mat2vec.h +++ b/src/connectome/mat2vec.h @@ -19,87 +19,112 @@ #define __connectome_mat2vec_h__ +#include #include +#include + +#include "types.h" + #include "connectome/connectome.h" + namespace MR { -namespace Connectome { + namespace Connectome { -class Mat2Vec -{ + class Mat2Vec + { - public: - Mat2Vec (const node_t); - Mat2Vec& operator= (Mat2Vec&&); + public: + Mat2Vec (const node_t); - size_t operator() (const node_t i, const node_t j) const - { - assert (i < size); - assert (j < size); - return lookup[i][j]; - } - std::pair operator() (const size_t i) const - { - assert (i < inv_lookup.size()); - return inv_lookup[i]; - } - node_t mat_size() const { return size; } - size_t vec_size() const { return inv_lookup.size(); } + Mat2Vec& operator= (Mat2Vec&&); - // Complete Matrix->Vector and Vector->Matrix conversion - // Templating allows use of either an Eigen::Vector or a std::vector - template Cont& operator() (const matrix_type&, Cont&) const; - template matrix_type& operator() (const Cont&, matrix_type&) const; + size_t operator() (const node_t i, const node_t j) const + { + assert (i < dim); + assert (j < dim); + return lookup[i][j]; + } + std::pair operator() (const size_t i) const + { + assert (i < inv_lookup.size()); + return inv_lookup[i]; + } + node_t mat_size() const { return dim; } + size_t vec_size() const { return inv_lookup.size(); } - protected: - node_t size; + // Complete Matrix->Vector and Vector->Matrix conversion + template + VecType& M2V (const MatType&, VecType&) const; + template + MatType& V2M (const VecType&, MatType&) const; - private: - // Lookup tables - std::vector< std::vector > lookup; - std::vector< std::pair > inv_lookup; + // Convenience functions to avoid having to pre-define the output class + template + vector_type M2V (const MatType&) const; + template + matrix_type V2M (const VecType&) const; -}; + private: + node_t dim; + // Lookup tables + std::vector< std::vector > lookup; + std::vector< std::pair > inv_lookup; + }; -template -Cont& Mat2Vec::operator() (const matrix_type& in, Cont& out) const -{ - assert (in.rows() == in.cols()); - assert (in.rows() == size); - out.resize (vec_size()); - for (size_t index = 0; index != vec_size(); ++index) { - const std::pair row_column = (*this) (index); - out[index] = in (row_column.first, row_column.second); - } - return out; -} + template + VecType& Mat2Vec::M2V (const MatType& m, VecType& v) const + { + assert (m.rows() == m.cols()); + assert (m.rows() == dim); + v.resize (vec_size()); + for (size_t index = 0; index != vec_size(); ++index) { + const std::pair row_col = (*this) (index); + v[index] = m (row_col.first, row_col.second); + } + return v; + } + template + MatType& Mat2Vec::V2M (const VecType& v, MatType& m) const + { + assert (v.size() == vec_size()); + m.resize (dim, dim); + for (node_t row = 0; row != dim; ++row) { + for (node_t col = 0; col != dim; ++col) + m (row, col) = v[(*this) (row, col)]; + } + return m; + } -template -matrix_type& Mat2Vec::operator() (const Cont& in, matrix_type& out) const -{ - assert (in.size() == vec_size()); - out.resize (size, size); - for (node_t row = 0; row != size; ++row) { - for (node_t column = 0; column != size; ++column) - out (row, column) = in[(*this) (row, column)]; - } - return out; -} + template + vector_type Mat2Vec::M2V (const MatType& m) const + { + vector_type v; + M2V (m, v); + return v; + } + template + matrix_type Mat2Vec::V2M (const VecType& v) const + { + matrix_type m; + V2M (v, m); + return m; + } -} + } } diff --git a/src/gui/mrview/tool/connectome/connectome.cpp b/src/gui/mrview/tool/connectome/connectome.cpp index d5b53afff9..0a6c94589b 100644 --- a/src/gui/mrview/tool/connectome/connectome.cpp +++ b/src/gui/mrview/tool/connectome/connectome.cpp @@ -2342,7 +2342,7 @@ namespace MR if (matrix.rows() != num_nodes()) throw Exception ("Matrix file \"" + Path::basename(list[i]) + "\" is incorrect size"); FileDataVector temp; - mat2vec (matrix, temp); + mat2vec.M2V (matrix, temp); temp.calc_stats(); temp.set_name (list[i]); data.push_back (std::move (temp)); @@ -2739,8 +2739,7 @@ namespace MR e.display(); return false; } - data.clear(); - mat2vec (temp, data); + mat2vec.M2V (temp, data); data.calc_stats(); data.set_name (Path::basename (path)); return true; From 013eb37244b07947fb13be5d39d492d09af82839 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 26 Jul 2016 16:39:18 +1000 Subject: [PATCH 038/723] New command: vectorstats Used to perform statistical inference where the data for each subject is contained within a 1D data file (e.g. one quantitative value for each node in the connectome). Does not currently compile as it is awaiting changes from another branch to be merged in. Closes #724. --- cmd/vectorstats.cpp | 193 +++++++++++++++++++++++++++++++++++++++++++ src/stats/permtest.h | 2 +- 2 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 cmd/vectorstats.cpp diff --git a/cmd/vectorstats.cpp b/cmd/vectorstats.cpp new file mode 100644 index 0000000000..320f5faf0a --- /dev/null +++ b/cmd/vectorstats.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + + +#include + +#include "command.h" +#include "progressbar.h" + +#include "file/path.h" +#include "math/stats/glm.h" +#include "math/stats/permutation.h" +#include "math/stats/typedefs.h" + +#include "stats/permtest.h" + + +using namespace MR; +using namespace App; + + +#define NPERMS_DEFAULT 5000 + + +void usage () +{ + AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; + + DESCRIPTION + + "Statistical testing of vector data using non-parametric permutation testing."; + + + ARGUMENTS + + Argument ("input", "a text file listing the file names of the input subject data").type_file_in () + + + Argument ("design", "the design matrix. Note that a column of 1's will need to be added for correlations.").type_file_in () + + + Argument ("contrast", "the contrast vector, specified as a single row of weights").type_file_in () + + + Argument ("output", "the filename prefix for all output.").type_text(); + + + OPTIONS + + + Option ("notest", "don't perform permutation testing and only output population statistics (effect size, stdev etc)") + + + Option ("nperms", "the number of permutations (default: " + str(NPERMS_DEFAULT) + ").") + + Argument ("num").type_integer (1); + +} + + + +using Math::Stats::matrix_type; +using Math::Stats::vector_type; + + + +void run() +{ + + // Read filenames + std::vector filenames; + { + std::string folder = Path::dirname (argument[0]); + std::ifstream ifs (argument[0].c_str()); + std::string temp; + while (getline (ifs, temp)) { + std::string filename (Path::join (folder, temp)); + size_t p = filename.find_last_not_of(" \t"); + if (std::string::npos != p) + filename.erase(p+1); + if (filename.size()) { + if (!MR::Path::exists (filename)) + throw Exception ("Input data vector file not found: \"" + filename + "\""); + filenames.push_back (filename); + } + } + } + + const vector_type example_data = load_vector (filenames.front()); + const size_t num_elements = example_data.size(); + + const size_t num_perms = get_option_value ("nperms", NPERMS_DEFAULT); + + // Load design matrix + const matrix_type design = load_matrix (argument[2]); + if (size_t(design.rows()) != filenames.size()) + throw Exception ("number of subjects does not match number of rows in design matrix"); + + // Load contrast matrix + matrix_type contrast = load_matrix (argument[3]); + if (contrast.cols() > design.cols()) + throw Exception ("too many contrasts for design matrix"); + contrast.conservativeResize (contrast.rows(), design.cols()); + + const std::string output_prefix = argument[4]; + + // Load input data + matrix_type data (num_elements, filenames.size()); + { + ProgressBar progress ("Loading input vector data", filenames.size()); + for (size_t subject = 0; subject < filenames.size(); subject++) { + + const std::string& path (filenames[subject]); + vector_type subject_data; + try { + subject_data = load_vector (path); + } catch (Exception& e) { + throw Exception (e, "Error loading vector data for subject #" + str(subject) + " (file \"" + path + "\""); + } + + if (size_t(subject_data.size()) != num_elements) + throw Exception ("Vector data for subject #" + str(subject) + " (file \"" + path + "\") is wrong length (" + str(subject_data.size()) + " , expected " + str(num_elements) + ")"); + + data.col(subject) = subject_data; + + ++progress; + } + } + + { + ProgressBar progress ("outputting beta coefficients, effect size and standard deviation...", contrast.cols() + 3); + + const matrix_type betas = Math::Stats::GLM::solve_betas (data, design); + for (size_t i = 0; i < size_t(contrast.cols()); ++i) { + save_vector (betas.col(i), output_prefix + "_beta_" + str(i) + ".csv"); + ++progress; + } + + const matrix_type abs_effects = Math::Stats::GLM::abs_effect_size (data, design, contrast); + save_vector (abs_effects.col(0), output_prefix + "_abs_effect.csv"); + ++progress; + + const matrix_type std_effects = Math::Stats::GLM::std_effect_size (data, design, contrast); + vector_type first_std_effect = std_effects.col(0); + for (size_t i = 0; i != num_elements; ++i) { + if (!std::isfinite (first_std_effect[i])) + first_std_effect[i] = 0.0; + } + save_vector (first_std_effect, output_prefix + "_std_effect.csv"); + ++progress; + + const matrix_type stdevs = Math::Stats::GLM::stdev (data, design); + save_vector (stdevs.col(0), output_prefix + "_std_dev.csv"); + } + + Math::Stats::GLMTTest glm_ttest (data, design, contrast); + + // Precompute default statistic + // Don't use convenience function: No enhancer! + // Manually construct default permutation + std::vector default_permutation (filenames.size()); + for (size_t i = 0; i != filenames.size(); ++i) + default_permutation[i] = i; + vector_type default_tvalues; + default_type max_stat, min_stat; + glm_ttest (default_permutation, default_tvalues, max_stat, min_stat); + save_vector (default_tvalues, output_prefix + "_tvalue.csv"); + + // Perform permutation testing + if (!get_options ("notest").size()) { + + vector_type null_distribution (num_perms), uncorrected_pvalues (num_perms); + vector_type empirical_distribution; + + // TODO Need the float_type_changes updates in order to bypass enhancement + Stats::PermTest::run_permutations (glm_ttest, nullptr, num_perms, empirical_distribution, + default_tvalues, std::shared_ptr(), + null_distribution, std::shared_ptr(), + uncorrected_pvalues, std::shared_ptr()); + + vector_type default_pvalues (num_elements); + Math::Stats::Permutation::statistic2pvalue (null_distribution, default_tvalues, default_pvalues); + save_vector (default_pvalues, output_prefix + "_fwe_pvalue.csv"); + save_vector (uncorrected_pvalues, output_prefix + "_uncorrected_pvalue.csv"); + + } + +} diff --git a/src/stats/permtest.h b/src/stats/permtest.h index 572880e9f8..2728ac9efa 100644 --- a/src/stats/permtest.h +++ b/src/stats/permtest.h @@ -297,7 +297,7 @@ namespace MR { PermutationStack permutations (num_permutations, stats_calculator.num_subjects(), - "running " + str(num_permutations) + " permutations..."); + "running " + str(num_permutations) + " permutations"); Processor processor (permutations, stats_calculator, enhancer, empirical_enhanced_statistic, From d78ec752b165c8e216c5716fdcdba7e261868e11 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 26 Jul 2016 19:54:18 +1000 Subject: [PATCH 039/723] vectorstats and connectomestats: Now working with latest changes to statistics code --- cmd/connectomestats.cpp | 57 ++++++-------------------------------- cmd/vectorstats.cpp | 3 +- src/connectome/enhance.cpp | 13 +++++---- src/connectome/enhance.h | 51 +++++++++++++--------------------- src/stats/permtest.h | 7 ++++- src/stats/tfce.h | 10 +++++++ 6 files changed, 52 insertions(+), 89 deletions(-) diff --git a/cmd/connectomestats.cpp b/cmd/connectomestats.cpp index 87e5076a18..02a94f9899 100644 --- a/cmd/connectomestats.cpp +++ b/cmd/connectomestats.cpp @@ -157,7 +157,7 @@ void run() const MR::Connectome::node_t num_nodes = example_connectome.rows(); // Initialise enhancement algorithm - std::shared_ptr enhancer; + std::shared_ptr enhancer; switch (int(argument[1])) { case 0: { auto opt = get_options ("threshold"); @@ -167,7 +167,7 @@ void run() } break; case 1: { - std::shared_ptr base (new MR::Connectome::Enhance::NBS (num_nodes)); + std::shared_ptr base (new MR::Connectome::Enhance::NBS (num_nodes)); enhancer.reset (new MR::Connectome::Enhance::TFCEWrapper (base)); load_tfce_parameters (*(dynamic_cast(enhancer.get()))); if (get_options ("threshold").size()) @@ -265,27 +265,10 @@ void run() Math::Stats::GLMTTest glm_ttest (data, design, contrast); - vector_type empirical_statistic; - // If performing non-stationarity adjustment we need to pre-compute the empirical statistic + vector_type empirical_statistic; if (do_nonstationary_adjustment) { - switch (int(argument[1])) { - case 0: { - MR::Connectome::Enhance::NBS* temp = dynamic_cast (enhancer.get()); - Stats::PermTest::precompute_empirical_stat (glm_ttest, *temp, nperms_nonstationary, empirical_statistic); - } - break; - case 1: { - MR::Connectome::Enhance::TFCEWrapper* temp = dynamic_cast (enhancer.get()); - Stats::PermTest::precompute_empirical_stat (glm_ttest, *temp, nperms_nonstationary, empirical_statistic); - } - break; - case 2: { - MR::Connectome::Enhance::PassThrough* temp = dynamic_cast (enhancer.get()); - Stats::PermTest::precompute_empirical_stat (glm_ttest, *temp, nperms_nonstationary, empirical_statistic); - } - break; - } + Stats::PermTest::precompute_empirical_stat (glm_ttest, enhancer, nperms_nonstationary, empirical_statistic); save_matrix (mat2vec.V2M (empirical_statistic), output_prefix + "_empirical.csv"); } @@ -293,7 +276,7 @@ void run() vector_type tvalue_output (num_edges); vector_type enhanced_output (num_edges); - Stats::PermTest::precompute_default_permutation (glm_ttest, *enhancer, empirical_statistic, enhanced_output, std::shared_ptr(), tvalue_output); + Stats::PermTest::precompute_default_permutation (glm_ttest, enhancer, empirical_statistic, enhanced_output, std::shared_ptr(), tvalue_output); save_matrix (mat2vec.V2M (tvalue_output), output_prefix + "_tvalue.csv"); save_matrix (mat2vec.V2M (enhanced_output), output_prefix + "_enhanced.csv"); @@ -306,32 +289,10 @@ void run() vector_type null_distribution (num_perms); vector_type uncorrected_pvalues (num_edges); - switch (int(argument[1])) { - - case 0: { - MR::Connectome::Enhance::NBS* temp = dynamic_cast (enhancer.get()); - Stats::PermTest::run_permutations (glm_ttest, *temp, num_perms, empirical_statistic, - enhanced_output, std::shared_ptr(), - null_distribution, std::shared_ptr(), - uncorrected_pvalues, std::shared_ptr()); - } - break; - case 1: { - MR::Connectome::Enhance::TFCEWrapper* temp = dynamic_cast (enhancer.get()); - Stats::PermTest::run_permutations (glm_ttest, *temp, num_perms, empirical_statistic, - enhanced_output, std::shared_ptr(), - null_distribution, std::shared_ptr(), - uncorrected_pvalues, std::shared_ptr()); - } break; - case 2: { - MR::Connectome::Enhance::PassThrough* temp = dynamic_cast (enhancer.get()); - Stats::PermTest::run_permutations (glm_ttest, *temp, num_perms, empirical_statistic, - enhanced_output, std::shared_ptr(), - null_distribution, std::shared_ptr(), - uncorrected_pvalues, std::shared_ptr()); - } - break; - } + Stats::PermTest::run_permutations (glm_ttest, enhancer, num_perms, empirical_statistic, + enhanced_output, std::shared_ptr(), + null_distribution, std::shared_ptr(), + uncorrected_pvalues, std::shared_ptr()); save_vector (null_distribution, output_prefix + "_null_dist.txt"); vector_type pvalue_output (num_edges); diff --git a/cmd/vectorstats.cpp b/cmd/vectorstats.cpp index 320f5faf0a..446d30f88b 100644 --- a/cmd/vectorstats.cpp +++ b/cmd/vectorstats.cpp @@ -167,8 +167,7 @@ void run() for (size_t i = 0; i != filenames.size(); ++i) default_permutation[i] = i; vector_type default_tvalues; - default_type max_stat, min_stat; - glm_ttest (default_permutation, default_tvalues, max_stat, min_stat); + glm_ttest (default_permutation, default_tvalues); save_vector (default_tvalues, output_prefix + "_tvalue.csv"); // Perform permutation testing diff --git a/src/connectome/enhance.cpp b/src/connectome/enhance.cpp index 52f01b6d52..5954ccdf3d 100644 --- a/src/connectome/enhance.cpp +++ b/src/connectome/enhance.cpp @@ -29,15 +29,15 @@ namespace MR { - value_type PassThrough::enhance (const value_type max_t, const vector_type& in, vector_type& out) const + value_type PassThrough::operator() (const vector_type& in, vector_type& out) const { out = in; - return max_t; + return out.maxCoeff(); } - value_type NBS::enhance_at_t (const vector_type& in, vector_type& out, const value_type T) const + value_type NBS::operator() (const vector_type& in, const value_type T, vector_type& out) const { out = vector_type::Zero (in.size()); value_type max_value = value_type(0); @@ -105,12 +105,13 @@ namespace MR { - value_type TFCEWrapper::enhance (const value_type max_t, const vector_type& in, vector_type& out) const + value_type TFCEWrapper::operator() (const vector_type& in, vector_type& out) const { out = vector_type::Zero (in.size()); - for (value_type h = dh; (h-dh) < max_t; h += dh) { + const value_type max_input_value = in.maxCoeff(); + for (value_type h = dh; (h-dh) < max_input_value; h += dh) { vector_type temp; - const value_type max = (*enhancer) (in, temp, h); + const value_type max = (*enhancer) (in, h, temp); if (max) { const value_type h_multiplier = std::pow (h, H); for (size_t index = 0; index != size_t(in.size()); ++index) diff --git a/src/connectome/enhance.h b/src/connectome/enhance.h index f6fa67dbbf..e5ea3cb315 100644 --- a/src/connectome/enhance.h +++ b/src/connectome/enhance.h @@ -25,6 +25,8 @@ #include "bitset.h" #include "connectome/mat2vec.h" +#include "stats/enhance.h" +#include "stats/tfce.h" @@ -34,41 +36,26 @@ namespace MR { - // TODO This should instead be handled using the new base enhancer class - // These changes must not have been pushed... - class Base - { - public: - Base() { } - Base (const Base& that) = default; - virtual ~Base() { } - - value_type operator() (const value_type max, const vector_type& in, vector_type& out) const { return enhance (max, in, out); } - virtual value_type operator() (const vector_type& in, vector_type& out, const value_type t) const { return enhance_at_t (in, out, t); } - - protected: - virtual value_type enhance (const value_type, const vector_type&, vector_type&) const { assert (0); return NaN; } - virtual value_type enhance_at_t (const vector_type&, vector_type&, const value_type) const { assert (0); return NaN; } - - }; + typedef Math::Stats::value_type value_type; + typedef Math::Stats::vector_type vector_type; - // This should be possible to use for either edge or node inference - class PassThrough : public Base + // This should be possible to use for any domain of inference + class PassThrough : public Stats::EnhancerBase { public: PassThrough() { } ~PassThrough() { } private: - value_type enhance (const value_type, const vector_type&, vector_type&) const override; + value_type operator() (const vector_type&, vector_type&) const override; }; - class NBS : public Base + class NBS : public Stats::TFCE::EnhancerBase { public: @@ -80,6 +67,12 @@ namespace MR { void set_threshold (const value_type t) { threshold = t; } + value_type operator() (const vector_type& in, vector_type& out) const override { + return (*this) (in, threshold, out); + } + + value_type operator() (const vector_type&, const value_type, vector_type&) const; + protected: std::shared_ptr< std::vector< std::vector > > adjacency; value_type threshold; @@ -87,20 +80,14 @@ namespace MR { private: void initialise (const node_t); - value_type enhance (const value_type max_t, const vector_type& in, vector_type& out) const override { - return enhance_at_t (in, out, threshold); - } - - value_type enhance_at_t (const vector_type&, vector_type&, const value_type) const override; - }; - class TFCEWrapper : public Base + class TFCEWrapper : public Stats::EnhancerBase { public: - TFCEWrapper (const std::shared_ptr base) : enhancer (base), E (NaN), H (NaN), dh (NaN) { } + TFCEWrapper (const std::shared_ptr base) : enhancer (base), E (NaN), H (NaN), dh (NaN) { } TFCEWrapper (const TFCEWrapper& that) = default; ~TFCEWrapper() { } @@ -112,12 +99,12 @@ namespace MR { dh = d_height; } + value_type operator() (const vector_type&, vector_type&) const override; + private: - std::shared_ptr enhancer; + std::shared_ptr enhancer; value_type E, H, dh; - value_type enhance (const value_type, const vector_type&, vector_type&) const override; - }; diff --git a/src/stats/permtest.h b/src/stats/permtest.h index 4bb7d87e76..9470aa50f3 100644 --- a/src/stats/permtest.h +++ b/src/stats/permtest.h @@ -135,7 +135,12 @@ namespace MR bool operator() (const Permutation& permutation) { stats_calculator (permutation.data, statistics); - perm_dist_pos[permutation.index] = (*enhancer) (statistics, enhanced_statistics); + if (enhancer) { + perm_dist_pos[permutation.index] = (*enhancer) (statistics, enhanced_statistics); + } else { + enhanced_statistics = statistics; + perm_dist_pos[permutation.index] = enhanced_statistics.maxCoeff(); + } if (empirical_enhanced_statistics.size()) { perm_dist_pos[permutation.index] = 0.0; diff --git a/src/stats/tfce.h b/src/stats/tfce.h index 19db155255..a3d094af6f 100644 --- a/src/stats/tfce.h +++ b/src/stats/tfce.h @@ -37,6 +37,16 @@ namespace MR + class EnhancerBase : public Stats::EnhancerBase + { + public: + // Alternative functor that also takes the threshold value + virtual value_type operator() (const vector_type& /*input_statistics*/, const value_type /*threshold*/, vector_type& /*enhanced_statistics*/) const = 0; + + }; + + + /** \addtogroup Statistics @{ */ class Enhancer : public Stats::EnhancerBase { From 6a23ad8685b7e1aef0a0331e13700194f058dc43 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 26 Jul 2016 22:59:31 +1000 Subject: [PATCH 040/723] Amalgamation of statistical inference code - Use common classes and command-line option groups between different commands that are used for performing statistical inference on different types of data. - Command vectorstats should now compile. - Implemented 'notest' option for mrclusterstats command. --- cmd/connectomestats.cpp | 42 ++----- cmd/fixelcfestats.cpp | 25 ++-- cmd/mrclusterstats.cpp | 119 +++++++++++--------- cmd/vectorstats.cpp | 14 +-- docs/reference/commands/connectomestats.rst | 21 +++- docs/reference/commands/fixelcfestats.rst | 21 +++- docs/reference/commands/mrclusterstats.rst | 29 +++-- docs/reference/commands/vectorstats.rst | 65 +++++++++++ docs/reference/commands_list.rst | 4 + src/connectome/enhance.cpp | 18 --- src/connectome/enhance.h | 27 ----- src/stats/cluster.cpp | 4 +- src/stats/cluster.h | 17 ++- src/stats/permtest.cpp | 51 +++++++++ src/stats/permtest.h | 10 ++ src/stats/tfce.cpp | 45 +++++--- src/stats/tfce.h | 39 +++++-- 17 files changed, 337 insertions(+), 214 deletions(-) create mode 100644 docs/reference/commands/vectorstats.rst create mode 100644 src/stats/permtest.cpp diff --git a/cmd/connectomestats.cpp b/cmd/connectomestats.cpp index 02a94f9899..97055b867c 100644 --- a/cmd/connectomestats.cpp +++ b/cmd/connectomestats.cpp @@ -45,9 +45,6 @@ const char* algorithms[] = { "nbs", "nbs_tfce", "none", nullptr }; #define TFCE_H_DEFAULT 3.0 -#define NPERMS_DEFAULT 5000 -#define NPERMS_NONSTATIONARITY_DEFAULT 5000 - void usage () { @@ -72,29 +69,15 @@ void usage () OPTIONS - + Option ("notest", "don't perform permutation testing and only output population statistics (effect size, stdev etc)") - - + Option ("nperms", "the number of permutations (default: " + str(NPERMS_DEFAULT) + ").") - + Argument ("num").type_integer (1) - - + Option ("threshold", "the t-statistic value to use in threshold-based clustering algorithms") - + Argument ("value").type_float (0.0) + + Stats::PermTest::Options (true) // TODO OptionGroup these, and provide a generic loader function - + Option ("tfce_dh", "the height increment used in the tfce integration (default: 0.1)") - + Argument ("value").type_float (1e-6) - - + Option ("tfce_e", "tfce extent exponent (default: 0.5)") - + Argument ("value").type_float (0.0) + + Stats::TFCE::Options (TFCE_DH_DEFAULT, TFCE_E_DEFAULT, TFCE_H_DEFAULT) - + Option ("tfce_h", "tfce height exponent (default: 2.0)") - + Argument ("value").type_float (0.0) - - + Option ("nonstationary", "do adjustment for non-stationarity") - - + Option ("nperms_nonstationary", "the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: " + str(NPERMS_NONSTATIONARITY_DEFAULT) + ")") - + Argument ("num").type_integer (1); + + OptionGroup ("Additional options for connectomestats") + + Option ("threshold", "the t-statistic value to use in threshold-based clustering algorithms") + + Argument ("value").type_float (0.0); REFERENCES + "* If using the NBS algorithm: \n" @@ -118,13 +101,12 @@ using Math::Stats::vector_type; - -void load_tfce_parameters (MR::Connectome::Enhance::TFCEWrapper& enhancer) +void load_tfce_parameters (Stats::TFCE::Wrapper& enhancer) { - const default_type dh = get_option_value ("tfce_dh", TFCE_DH_DEFAULT); + 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 (E, H, dh); + enhancer.set_tfce_parameters (dH, E, H); } @@ -168,8 +150,8 @@ void run() break; case 1: { std::shared_ptr base (new MR::Connectome::Enhance::NBS (num_nodes)); - enhancer.reset (new MR::Connectome::Enhance::TFCEWrapper (base)); - load_tfce_parameters (*(dynamic_cast(enhancer.get()))); + 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"); } @@ -184,9 +166,9 @@ void run() throw Exception ("Unknown enhancement algorithm"); } - const size_t num_perms = get_option_value ("nperms", NPERMS_DEFAULT); + const size_t num_perms = get_option_value ("nperms", DEFAULT_NUMBER_PERMUTATIONS); const bool do_nonstationary_adjustment = get_options ("nonstationary").size(); - const size_t nperms_nonstationary = get_option_value ("nperms_nonstationarity", NPERMS_NONSTATIONARITY_DEFAULT); + const size_t nperms_nonstationary = get_option_value ("nperms_nonstationarity", DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY); // Load design matrix const matrix_type design = load_matrix (argument[2]); diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 722416cfec..41fc8db8b3 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -43,7 +43,6 @@ using Sparse::FixelMetric; using Stats::CFE::direction_type; using Stats::CFE::connectivity_value_type; -#define DEFAULT_PERMUTATIONS 5000 #define DEFAULT_CFE_DH 0.1 #define DEFAULT_CFE_E 2.0 #define DEFAULT_CFE_H 3.0 @@ -51,7 +50,6 @@ using Stats::CFE::connectivity_value_type; #define DEFAULT_ANGLE_THRESHOLD 30.0 #define DEFAULT_CONNECTIVITY_THRESHOLD 0.01 #define DEFAULT_SMOOTHING_STD 10.0 -#define DEFAULT_PERMUTATIONS_NONSTATIONARITY 5000 void usage () { @@ -87,13 +85,9 @@ void usage () OPTIONS - + Option ("notest", "don't perform permutation testing and only output population statistics (effect size, stdev etc)") + + Stats::PermTest::Options (true) - + Option ("negative", "automatically test the negative (opposite) contrast. By computing the opposite contrast simultaneously " - "the computation time is reduced.") - - + Option ("nperms", "the number of permutations (default: " + str(DEFAULT_PERMUTATIONS) + ").") - + Argument ("num").type_integer (1) + + 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) @@ -107,6 +101,11 @@ void usage () + Option ("cfe_c", "cfe connectivity exponent (default: " + str(DEFAULT_CFE_C, 2) + ")") + Argument ("value").type_float (0.0, 100.0) + + OptionGroup ("Additional options for fixelcfestats") + + + Option ("negative", "automatically test the negative (opposite) contrast. By computing the opposite contrast simultaneously " + "the computation time is reduced.") + + 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) @@ -114,12 +113,8 @@ void usage () + Argument ("threshold").type_float (0.0, 1.0) + Option ("smooth", "smooth the fixel value along the fibre tracts using a Gaussian kernel with the supplied FWHM (default: " + str(DEFAULT_SMOOTHING_STD, 2) + "mm)") - + Argument ("FWHM").type_float (0.0, 200.0) - - + Option ("nonstationary", "do adjustment for non-stationarity") + + Argument ("FWHM").type_float (0.0, 200.0); - + Option ("nperms_nonstationary", "the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: " + str(DEFAULT_PERMUTATIONS_NONSTATIONARITY) + ")") - + Argument ("num").type_integer (1); } @@ -154,7 +149,7 @@ void run() { 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 int num_perms = get_option_value ("nperms", DEFAULT_PERMUTATIONS); + const int num_perms = get_option_value ("nperms", DEFAULT_NUMBER_PERMUTATIONS); const value_type angular_threshold = get_option_value ("angle", DEFAULT_ANGLE_THRESHOLD); const value_type angular_threshold_dp = cos (angular_threshold * (Math::pi/180.0)); @@ -163,7 +158,7 @@ void run() { const bool do_nonstationary_adjustment = get_options ("nonstationary").size(); - const int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_PERMUTATIONS_NONSTATIONARITY); + const int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY); // Read filenames std::vector filenames; diff --git a/cmd/mrclusterstats.cpp b/cmd/mrclusterstats.cpp index 087c0c1625..86f2bf4a93 100644 --- a/cmd/mrclusterstats.cpp +++ b/cmd/mrclusterstats.cpp @@ -35,11 +35,9 @@ using namespace App; using namespace MR::Math::Stats; -#define DEFAULT_PERMUTATIONS 5000 #define DEFAULT_TFCE_DH 0.1 #define DEFAULT_TFCE_H 2.0 #define DEFAULT_TFCE_E 0.5 -#define DEFAULT_PERMUTATIONS_NONSTATIONARITY 5000 void usage () @@ -73,31 +71,20 @@ void usage () OPTIONS - + Option ("negative", "automatically test the negative (opposite) contrast. By computing the opposite contrast simultaneously " - "the computation time is reduced.") + + Stats::PermTest::Options (true) - + Option ("nperms", "the number of permutations (default = " + str(DEFAULT_PERMUTATIONS) + ").") - + Argument ("num").type_integer (1) + + Stats::TFCE::Options (DEFAULT_TFCE_DH, DEFAULT_TFCE_E, DEFAULT_TFCE_H) - + 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 ("tfce_dh", "the height increment used in the TFCE integration (default = " + str(DEFAULT_TFCE_DH, 2) + ")") - + Argument ("value").type_float (0.001, 1.0) + + Option ("negative", "automatically test the negative (opposite) contrast. By computing the opposite contrast simultaneously " + "the computation time is reduced.") - + Option ("tfce_e", "TFCE extent parameter (default = " + str(DEFAULT_TFCE_E, 2) + ")") - + Argument ("value").type_float (0.001, 100.0) + + 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 ("tfce_h", "TFCE height parameter (default = " + str(DEFAULT_TFCE_H, 2) + ")") - + Argument ("value").type_float (0.001, 100.0) - - + Option ("connectivity", "use 26-voxel-neighbourhood connectivity (Default: 6)") - - + Option ("nonstationary", "perform non-stationarity correction (currently only implemented with tfce)") - - + Option ("nperms_nonstationary", "the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: " + str(DEFAULT_PERMUTATIONS_NONSTATIONARITY) + ")") - + Argument ("num").type_integer (1); + + Option ("connectivity", "use 26-voxel-neighbourhood connectivity (Default: 6)"); } @@ -110,8 +97,8 @@ void run() { 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 int num_perms = get_option_value ("nperms", DEFAULT_PERMUTATIONS); - const int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_PERMUTATIONS_NONSTATIONARITY); + const int num_perms = get_option_value ("nperms", DEFAULT_NUMBER_PERMUTATIONS); + const int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY); const bool do_26_connectivity = get_options("connectivity").size(); const bool do_nonstationary_adjustment = get_options ("nonstationary").size(); @@ -209,60 +196,80 @@ void run() { } std::shared_ptr enhancer; - if (use_tfce) - enhancer.reset (new Stats::TFCE::Enhancer (connector, tfce_dh, tfce_E, tfce_H)); - else + 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)); + } else { enhancer.reset (new Stats::Cluster::ClusterSize (connector, cluster_forming_threshold)); + } - { // Do permutation testing: - Math::Stats::GLMTTest glm (data, design, contrast); - - if (do_nonstationary_adjustment) { - if (!use_tfce) - throw Exception ("nonstationary adjustment is not currently implemented for threshold-based cluster analysis"); - Stats::PermTest::precompute_empirical_stat (glm, enhancer, nperms_nonstationary, empirical_enhanced_statistic); - } - - Stats::PermTest::precompute_default_permutation (glm, enhancer, empirical_enhanced_statistic, - default_cluster_output, default_cluster_output_neg, tvalue_output); + Math::Stats::GLMTTest glm (data, design, contrast); - Stats::PermTest::run_permutations (glm, enhancer, num_perms, empirical_enhanced_statistic, - default_cluster_output, default_cluster_output_neg, - perm_distribution, perm_distribution_neg, - uncorrected_pvalue, uncorrected_pvalue_neg); + if (do_nonstationary_adjustment) { + if (!use_tfce) + throw Exception ("nonstationary adjustment is not currently implemented for threshold-based cluster analysis"); + Stats::PermTest::precompute_empirical_stat (glm, enhancer, nperms_nonstationary, empirical_enhanced_statistic); } - save_matrix (perm_distribution, prefix + "perm_dist.txt"); + Stats::PermTest::precompute_default_permutation (glm, enhancer, empirical_enhanced_statistic, + default_cluster_output, default_cluster_output_neg, tvalue_output); - vector_type pvalue_output (num_vox); - Math::Stats::Permutation::statistic2pvalue (perm_distribution, default_cluster_output, pvalue_output); { - ProgressBar progress ("generating output", num_vox); + ProgressBar progress ("generating pre-permutation output", num_vox); for (size_t i = 0; i < num_vox; i++) { for (size_t dim = 0; dim < cluster_image.ndim(); dim++) - tvalue_image.index(dim) = cluster_image.index(dim) = fwe_pvalue_image.index(dim) = uncorrected_pvalue_image.index(dim) = mask_indices[i][dim]; + tvalue_image.index(dim) = cluster_image.index(dim) = uncorrected_pvalue_image.index(dim) = mask_indices[i][dim]; tvalue_image.value() = tvalue_output[i]; cluster_image.value() = default_cluster_output[i]; - fwe_pvalue_image.value() = pvalue_output[i]; uncorrected_pvalue_image.value() = uncorrected_pvalue[i]; ++progress; } } - if (compute_negative_contrast) { - save_matrix (*perm_distribution_neg, prefix + "perm_dist_neg.txt"); - vector_type pvalue_output_neg (num_vox); - Math::Stats::Permutation::statistic2pvalue (*perm_distribution_neg, *default_cluster_output_neg, pvalue_output_neg); - - ProgressBar progress ("generating negative contrast output", num_vox); + ProgressBar progress ("generating pre-permutation negative contrast output", num_vox); for (size_t i = 0; i < num_vox; i++) { for (size_t dim = 0; dim < cluster_image.ndim(); dim++) - cluster_image_neg.index(dim) = fwe_pvalue_image_neg.index(dim) = uncorrected_pvalue_image_neg.index(dim) = mask_indices[i][dim]; + cluster_image_neg.index(dim) = uncorrected_pvalue_image_neg.index(dim) = mask_indices[i][dim]; cluster_image_neg.value() = (*default_cluster_output_neg)[i]; - fwe_pvalue_image_neg.value() = pvalue_output_neg[i]; uncorrected_pvalue_image_neg.value() = (*uncorrected_pvalue_neg)[i]; ++progress; } } + if (!get_options ("notest").size()) { + + Stats::PermTest::run_permutations (glm, enhancer, num_perms, empirical_enhanced_statistic, + default_cluster_output, default_cluster_output_neg, + perm_distribution, perm_distribution_neg, + uncorrected_pvalue, uncorrected_pvalue_neg); + + save_matrix (perm_distribution, prefix + "perm_dist.txt"); + + vector_type pvalue_output (num_vox); + Math::Stats::Permutation::statistic2pvalue (perm_distribution, default_cluster_output, pvalue_output); + { + ProgressBar progress ("generating output", num_vox); + for (size_t i = 0; i < num_vox; i++) { + for (size_t dim = 0; dim < cluster_image.ndim(); dim++) + fwe_pvalue_image.index(dim) = mask_indices[i][dim]; + fwe_pvalue_image.value() = pvalue_output[i]; + ++progress; + } + } + + if (compute_negative_contrast) { + save_matrix (*perm_distribution_neg, prefix + "perm_dist_neg.txt"); + vector_type pvalue_output_neg (num_vox); + Math::Stats::Permutation::statistic2pvalue (*perm_distribution_neg, *default_cluster_output_neg, pvalue_output_neg); + + ProgressBar progress ("generating negative contrast output", num_vox); + for (size_t i = 0; i < num_vox; i++) { + for (size_t dim = 0; dim < cluster_image.ndim(); dim++) + fwe_pvalue_image_neg.index(dim) = mask_indices[i][dim]; + fwe_pvalue_image_neg.value() = pvalue_output_neg[i]; + ++progress; + } + } + } + } diff --git a/cmd/vectorstats.cpp b/cmd/vectorstats.cpp index 446d30f88b..7615916c4b 100644 --- a/cmd/vectorstats.cpp +++ b/cmd/vectorstats.cpp @@ -32,8 +32,6 @@ using namespace MR; using namespace App; -#define NPERMS_DEFAULT 5000 - void usage () { @@ -54,11 +52,7 @@ void usage () OPTIONS - - + Option ("notest", "don't perform permutation testing and only output population statistics (effect size, stdev etc)") - - + Option ("nperms", "the number of permutations (default: " + str(NPERMS_DEFAULT) + ").") - + Argument ("num").type_integer (1); + + Stats::PermTest::Options (false); } @@ -94,7 +88,7 @@ void run() const vector_type example_data = load_vector (filenames.front()); const size_t num_elements = example_data.size(); - const size_t num_perms = get_option_value ("nperms", NPERMS_DEFAULT); + const size_t num_perms = get_option_value ("nperms", DEFAULT_NUMBER_PERMUTATIONS); // Load design matrix const matrix_type design = load_matrix (argument[2]); @@ -173,11 +167,11 @@ void run() // Perform permutation testing if (!get_options ("notest").size()) { + std::shared_ptr enhancer; vector_type null_distribution (num_perms), uncorrected_pvalues (num_perms); vector_type empirical_distribution; - // TODO Need the float_type_changes updates in order to bypass enhancement - Stats::PermTest::run_permutations (glm_ttest, nullptr, num_perms, empirical_distribution, + Stats::PermTest::run_permutations (glm_ttest, enhancer, num_perms, empirical_distribution, default_tvalues, std::shared_ptr(), null_distribution, std::shared_ptr(), uncorrected_pvalues, std::shared_ptr()); diff --git a/docs/reference/commands/connectomestats.rst b/docs/reference/commands/connectomestats.rst index e479da71ee..8cecb82ea4 100644 --- a/docs/reference/commands/connectomestats.rst +++ b/docs/reference/commands/connectomestats.rst @@ -24,21 +24,30 @@ Connectome group-wise statistics at the edge level using non-parametric permutat Options ------- +Options for permutation testing +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + - **-notest** don't perform permutation testing and only output population statistics (effect size, stdev etc) -- **-nperms num** the number of permutations (default: 5000). +- **-nperms num** the number of permutations (Default: 5000) -- **-threshold value** the t-statistic value to use in threshold-based clustering algorithms +- **-nonstationary** perform non-stationarity correction + +- **-nperms_nonstationary num** the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: 5000) + +Options for controlling TFCE behaviour +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - **-tfce_dh value** the height increment used in the tfce integration (default: 0.1) -- **-tfce_e value** tfce extent exponent (default: 0.5) +- **-tfce_e value** tfce extent exponent (default: 0.4) -- **-tfce_h value** tfce height exponent (default: 2.0) +- **-tfce_h value** tfce height exponent (default: 3) -- **-nonstationary** do adjustment for non-stationarity +Additional options for connectomestats +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- **-nperms_nonstationary num** the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: 5000) +- **-threshold value** the t-statistic value to use in threshold-based clustering algorithms Standard options ^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/fixelcfestats.rst b/docs/reference/commands/fixelcfestats.rst index b904d1a377..32e4ef2346 100644 --- a/docs/reference/commands/fixelcfestats.rst +++ b/docs/reference/commands/fixelcfestats.rst @@ -25,11 +25,19 @@ Fixel-based analysis using connectivity-based fixel enhancement and non-parametr Options ------- +Options for permutation testing +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + - **-notest** don't perform permutation testing and only output population statistics (effect size, stdev etc) -- **-negative** automatically test the negative (opposite) contrast. By computing the opposite contrast simultaneously the computation time is reduced. +- **-nperms num** the number of permutations (Default: 5000) + +- **-nonstationary** perform non-stationarity correction -- **-nperms num** the number of permutations (default: 5000). +- **-nperms_nonstationary num** the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: 5000) + +Parameters for the Connectivity-based Fixel Enhancement algorithm +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - **-cfe_dh value** the height increment used in the cfe integration (default: 0.1) @@ -39,16 +47,17 @@ Options - **-cfe_c value** cfe connectivity exponent (default: 0.5) +Additional options for fixelcfestats +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-negative** automatically test the negative (opposite) contrast. By computing the opposite contrast simultaneously the computation time is reduced. + - **-angle value** the max angle threshold for computing inter-subject fixel correspondence (Default: 30 degrees) - **-connectivity threshold** a threshold to define the required fraction of shared connections to be included in the neighbourhood (default: 0.01) - **-smooth FWHM** smooth the fixel value along the fibre tracts using a Gaussian kernel with the supplied FWHM (default: 10mm) -- **-nonstationary** do adjustment for non-stationarity - -- **-nperms_nonstationary num** the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: 5000) - Standard options ^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/mrclusterstats.rst b/docs/reference/commands/mrclusterstats.rst index b22def677f..7d78dc5bdd 100644 --- a/docs/reference/commands/mrclusterstats.rst +++ b/docs/reference/commands/mrclusterstats.rst @@ -24,23 +24,34 @@ Voxel-based analysis using permutation testing and threshold-free cluster enhanc Options ------- -- **-negative** automatically test the negative (opposite) contrast. By computing the opposite contrast simultaneously the computation time is reduced. +Options for permutation testing +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- **-nperms num** the number of permutations (default = 5000). +- **-notest** don't perform permutation testing and only output population statistics (effect size, stdev etc) -- **-threshold value** the cluster-forming threshold to use for a standard cluster-based analysis. This disables TFCE, which is the default otherwise. +- **-nperms num** the number of permutations (Default: 5000) + +- **-nonstationary** perform non-stationarity correction -- **-tfce_dh value** the height increment used in the TFCE integration (default = 0.1) +- **-nperms_nonstationary num** the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: 5000) -- **-tfce_e value** TFCE extent parameter (default = 0.5) +Options for controlling TFCE behaviour +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- **-tfce_h value** TFCE height parameter (default = 2) +- **-tfce_dh value** the height increment used in the tfce integration (default: 0.1) -- **-connectivity** use 26-voxel-neighbourhood connectivity (Default: 6) +- **-tfce_e value** tfce extent exponent (default: 0.5) -- **-nonstationary** perform non-stationarity correction (currently only implemented with tfce) +- **-tfce_h value** tfce height exponent (default: 2) -- **-nperms_nonstationary num** the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: 5000) +Additional options for mrclusterstats +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-negative** automatically test the negative (opposite) contrast. By computing the opposite contrast simultaneously the computation time is reduced. + +- **-threshold value** the cluster-forming threshold to use for a standard cluster-based analysis. This disables TFCE, which is the default otherwise. + +- **-connectivity** use 26-voxel-neighbourhood connectivity (Default: 6) Standard options ^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/vectorstats.rst b/docs/reference/commands/vectorstats.rst new file mode 100644 index 0000000000..a45300cb30 --- /dev/null +++ b/docs/reference/commands/vectorstats.rst @@ -0,0 +1,65 @@ +.. _vectorstats: + +vectorstats +=========== + +Synopsis +-------- + +:: + + vectorstats [ options ] input design contrast output + +- *input*: a text file listing the file names of the input subject data +- *design*: the design matrix. Note that a column of 1's will need to be added for correlations. +- *contrast*: the contrast vector, specified as a single row of weights +- *output*: the filename prefix for all output. + +Description +----------- + +Statistical testing of vector data using non-parametric permutation testing. + +Options +------- + +Options for permutation testing +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-notest** don't perform permutation testing and only output population statistics (effect size, stdev etc) + +- **-nperms num** the number of permutations (Default: 5000) + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) + +- **-failonwarn** terminate program if a warning is produced + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +-------------- + + + +**Author:** Robert E. Smith (robert.smith@florey.edu.au) + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index edcc8e4bcc..7d22ba1d9d 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -363,6 +363,10 @@ List of MRtrix3 commands ....... +.. include:: commands/vectorstats.rst +....... + + .. include:: commands/voxel2fixel.rst ....... diff --git a/src/connectome/enhance.cpp b/src/connectome/enhance.cpp index 5954ccdf3d..c14d737a11 100644 --- a/src/connectome/enhance.cpp +++ b/src/connectome/enhance.cpp @@ -105,24 +105,6 @@ namespace MR { - value_type TFCEWrapper::operator() (const vector_type& in, vector_type& out) const - { - out = vector_type::Zero (in.size()); - const value_type max_input_value = in.maxCoeff(); - for (value_type h = dh; (h-dh) < max_input_value; h += dh) { - vector_type temp; - const value_type max = (*enhancer) (in, h, temp); - if (max) { - const value_type h_multiplier = std::pow (h, H); - for (size_t index = 0; index != size_t(in.size()); ++index) - out[index] += (std::pow (temp[index], E) * h_multiplier); - } - } - return out.maxCoeff(); - } - - - } } } diff --git a/src/connectome/enhance.h b/src/connectome/enhance.h index e5ea3cb315..88dd57f1cd 100644 --- a/src/connectome/enhance.h +++ b/src/connectome/enhance.h @@ -84,33 +84,6 @@ namespace MR { - class TFCEWrapper : public Stats::EnhancerBase - { - public: - TFCEWrapper (const std::shared_ptr base) : enhancer (base), E (NaN), H (NaN), dh (NaN) { } - TFCEWrapper (const TFCEWrapper& that) = default; - ~TFCEWrapper() { } - - // TODO Homogenise TFCE - void set_tfce_parameters (const value_type extent, const value_type height, const value_type d_height) - { - E = extent; - H = height; - dh = d_height; - } - - value_type operator() (const vector_type&, vector_type&) const override; - - private: - std::shared_ptr enhancer; - value_type E, H, dh; - - }; - - - - - } } } diff --git a/src/stats/cluster.cpp b/src/stats/cluster.cpp index 4f4e032be5..bbd319cd77 100644 --- a/src/stats/cluster.cpp +++ b/src/stats/cluster.cpp @@ -26,11 +26,11 @@ namespace MR - value_type ClusterSize::operator() (const vector_type& stats, vector_type& get_cluster_sizes) const + value_type ClusterSize::operator() (const vector_type& stats, const value_type T, vector_type& get_cluster_sizes) const { std::vector clusters; std::vector labels (stats.size(), 0); - connector.run (clusters, labels, stats, cluster_forming_threshold); + connector.run (clusters, labels, stats, T); get_cluster_sizes.resize (stats.size()); for (size_t i = 0; i < size_t(stats.size()); ++i) get_cluster_sizes[i] = labels[i] ? clusters[labels[i]-1].size : 0.0; diff --git a/src/stats/cluster.h b/src/stats/cluster.h index 1df0701fce..2683d15884 100644 --- a/src/stats/cluster.h +++ b/src/stats/cluster.h @@ -19,6 +19,7 @@ #include "filter/connected_components.h" #include "math/stats/typedefs.h" +#include "stats/tfce.h" #include "stats/enhance.h" namespace MR @@ -37,18 +38,24 @@ namespace MR /** \addtogroup Statistics @{ */ - class ClusterSize : public Stats::EnhancerBase { + class ClusterSize : public Stats::TFCE::EnhancerBase { public: - ClusterSize (const Filter::Connector& connector, value_type cluster_forming_threshold) : - connector (connector), cluster_forming_threshold (cluster_forming_threshold) { } + ClusterSize (const Filter::Connector& connector, const value_type T) : + connector (connector), threshold (T) { } + void set_threshold (const value_type T) { threshold = T; } - value_type operator() (const vector_type& stats, vector_type& get_cluster_sizes) const override; + + value_type operator() (const vector_type& in, vector_type& out) const override { + return (*this) (in, threshold, out); + } + + value_type operator() (const vector_type&, const value_type, vector_type&) const override; protected: const Filter::Connector& connector; - const value_type cluster_forming_threshold; + value_type threshold; }; //! @} diff --git a/src/stats/permtest.cpp b/src/stats/permtest.cpp new file mode 100644 index 0000000000..632010bb8c --- /dev/null +++ b/src/stats/permtest.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "stats/permtest.h" + +namespace MR +{ + namespace Stats + { + namespace PermTest + { + + + + const App::OptionGroup Options (const bool include_nonstationarity) + { + using namespace App; + + OptionGroup result = OptionGroup ("Options for permutation testing") + + Option ("notest", "don't perform permutation testing and only output population statistics (effect size, stdev etc)") + + Option ("nperms", "the number of permutations (Default: " + str(DEFAULT_NUMBER_PERMUTATIONS) + ")") + + Argument ("num").type_integer (1); + + if (include_nonstationarity) { + result + + Option ("nonstationary", "perform non-stationarity correction") + + Option ("nperms_nonstationary", "the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: " + str(DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY) + ")") + + Argument ("num").type_integer (1); + } + + return result; + } + + + + } + } +} + diff --git a/src/stats/permtest.h b/src/stats/permtest.h index 9470aa50f3..92e1ddd939 100644 --- a/src/stats/permtest.h +++ b/src/stats/permtest.h @@ -19,6 +19,7 @@ #include #include +#include "app.h" #include "progressbar.h" #include "thread.h" #include "thread_queue.h" @@ -29,6 +30,11 @@ #include "stats/enhance.h" #include "stats/permstack.h" + +#define DEFAULT_NUMBER_PERMUTATIONS 5000 +#define DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY 5000 + + namespace MR { namespace Stats @@ -43,6 +49,10 @@ namespace MR + const App::OptionGroup Options (const bool include_nonstationarity); + + + /*! A class to pre-compute the empirical enhanced statistic image for non-stationarity correction */ template class PreProcessor { diff --git a/src/stats/tfce.cpp b/src/stats/tfce.cpp index d4a6583111..ea64d7fe06 100644 --- a/src/stats/tfce.cpp +++ b/src/stats/tfce.cpp @@ -23,30 +23,39 @@ namespace MR { + using namespace App; + const OptionGroup Options (const default_type default_dh, const default_type default_e, const default_type default_h) + { + OptionGroup result = OptionGroup ("Options for controlling TFCE behaviour") + + + Option ("tfce_dh", "the height increment used in the tfce integration (default: " + str(default_dh, 2) + ")") + + Argument ("value").type_float (1e-6) + + + Option ("tfce_e", "tfce extent exponent (default: " + str(default_e, 2) + ")") + + Argument ("value").type_float (0.0) - Enhancer::Enhancer (const Filter::Connector& connector, const value_type dh, const value_type E, const value_type H) : - connector (connector), - dh (dh), - E (E), - H (H) { } + + Option ("tfce_h", "tfce height exponent (default: " + str(default_h, 2) + ")") + + Argument ("value").type_float (0.0); + + return result; + } - value_type Enhancer::operator() (const vector_type& stats, vector_type& enhanced_stats) const + value_type Wrapper::operator() (const vector_type& in, vector_type& out) const { - enhanced_stats = vector_type::Zero (stats.size()); - const value_type max_stat = stats.maxCoeff(); - - for (value_type h = this->dh; h < max_stat; h += this->dh) { - std::vector clusters; - std::vector labels (enhanced_stats.size(), 0); - connector.run (clusters, labels, stats, h); - for (size_t i = 0; i < size_t(enhanced_stats.size()); ++i) - if (labels[i]) - enhanced_stats[i] += pow (clusters[labels[i]-1].size, this->E) * pow (h, this->H); + out = vector_type::Zero (in.size()); + const value_type max_input_value = in.maxCoeff(); + for (value_type h = dH; (h-dH) < max_input_value; h += dH) { + vector_type temp; + const value_type max = (*enhancer) (in, h, temp); + if (max) { + const value_type h_multiplier = std::pow (h, H); + for (size_t index = 0; index != size_t(in.size()); ++index) + out[index] += (std::pow (temp[index], E) * h_multiplier); + } } - - return enhanced_stats.maxCoeff(); + return out.maxCoeff(); } diff --git a/src/stats/tfce.h b/src/stats/tfce.h index a3d094af6f..ba2fb4d3f5 100644 --- a/src/stats/tfce.h +++ b/src/stats/tfce.h @@ -32,6 +32,11 @@ namespace MR + const App::OptionGroup Options (const default_type, const default_type, const default_type); + + + + typedef Math::Stats::value_type value_type; typedef Math::Stats::vector_type vector_type; @@ -40,26 +45,36 @@ namespace MR class EnhancerBase : public Stats::EnhancerBase { public: - // Alternative functor that also takes the threshold value + // Alternative functor that also takes the threshold value; + // makes TFCE integration cleaner virtual value_type operator() (const vector_type& /*input_statistics*/, const value_type /*threshold*/, vector_type& /*enhanced_statistics*/) const = 0; }; - /** \addtogroup Statistics - @{ */ - class Enhancer : public Stats::EnhancerBase { - public: - Enhancer (const Filter::Connector& connector, const value_type dh, const value_type E, const value_type H); - - value_type operator() (const vector_type& stats, vector_type& enhanced_stats) const override; - protected: - const Filter::Connector& connector; - const value_type dh, E, H; + class Wrapper : public Stats::EnhancerBase + { + public: + Wrapper (const std::shared_ptr base) : enhancer (base), dH (NaN), E (NaN), H (NaN) { } + Wrapper (const std::shared_ptr base, const default_type dh, const default_type e, const default_type h) : enhancer (base), dH (dh), E (e), H (h) { } + Wrapper (const Wrapper& that) = default; + ~Wrapper() { } + + void set_tfce_parameters (const value_type d_height, const value_type extent, const value_type height) + { + dH = d_height; + E = extent; + H = height; + } + + value_type operator() (const vector_type&, vector_type&) const override; + + private: + std::shared_ptr enhancer; + value_type dH, E, H; }; - //! @} From dfd3e6d478ac3f32eaf785f98a1b1ef260ef4fa9 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 27 Jul 2016 15:06:57 +1000 Subject: [PATCH 041/723] Histogram commands: Use existing histogram file as template --- cmd/fixelhistogram.cpp | 28 +++++++++++++--------- cmd/mrhistogram.cpp | 19 +++++++++------ docs/reference/commands/fixelhistogram.rst | 2 ++ docs/reference/commands/mrhistogram.rst | 2 ++ lib/algo/histogram.cpp | 28 ++++++++++++++++++++++ lib/algo/histogram.h | 10 +++++--- 6 files changed, 68 insertions(+), 21 deletions(-) diff --git a/cmd/fixelhistogram.cpp b/cmd/fixelhistogram.cpp index fd2dcf4c84..adf21872c0 100644 --- a/cmd/fixelhistogram.cpp +++ b/cmd/fixelhistogram.cpp @@ -60,22 +60,28 @@ void run () size_t nbins = get_option_value ("bins", 0); Algo::Histogram::Calibrator calibrator (nbins, get_options ("ignorezero").size()); - for (auto i = Loop (input) (input); i; ++i) { - if (mask_ptr) { - assign_pos_of (input).to (*mask_ptr); - if (input.value().size() != mask_ptr->value().size()) - throw Exception ("the input fixel image and mask image to not have corrresponding fixels"); - } - for (size_t fixel = 0; fixel != input.value().size(); ++fixel) { + + opt = get_options ("template"); + if (opt.size()) { + calibrator.from_file (opt[0][0]); + } else { + for (auto i = Loop (input) (input); i; ++i) { if (mask_ptr) { - if (mask_ptr->value()[fixel].value > 0.5) + assign_pos_of (input).to (*mask_ptr); + if (input.value().size() != mask_ptr->value().size()) + throw Exception ("the input fixel image and mask image to not have corrresponding fixels"); + } + for (size_t fixel = 0; fixel != input.value().size(); ++fixel) { + if (mask_ptr) { + if (mask_ptr->value()[fixel].value > 0.5) + calibrator (input.value()[fixel].value); + } else { calibrator (input.value()[fixel].value); - } else { - calibrator (input.value()[fixel].value); + } } } + calibrator.finalize (1, false); } - calibrator.finalize (1, false); Algo::Histogram::Data histogram (calibrator); diff --git a/cmd/mrhistogram.cpp b/cmd/mrhistogram.cpp index 5a78846cc2..c72cbb659a 100644 --- a/cmd/mrhistogram.cpp +++ b/cmd/mrhistogram.cpp @@ -120,13 +120,18 @@ void run () File::OFStream output (argument[1]); Algo::Histogram::Calibrator calibrator (nbins, get_options ("ignorezero").size()); - for (auto v = Volume_loop(data); v; ++v) - 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); + opt = get_options ("template"); + if (opt.size()) { + calibrator.from_file (opt[0][0]); + } else { + for (auto v = Volume_loop(data); v; ++v) + 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); + } nbins = calibrator.get_num_bins(); for (size_t i = 0; i != nbins; ++i) diff --git a/docs/reference/commands/fixelhistogram.rst b/docs/reference/commands/fixelhistogram.rst index 05696b7b40..229816a03e 100644 --- a/docs/reference/commands/fixelhistogram.rst +++ b/docs/reference/commands/fixelhistogram.rst @@ -25,6 +25,8 @@ Histogram generation options - **-bins num** Manually set the number of bins to use to generate the histogram. +- **-template file** Use an existing histogram file as the template for histogram formation + - **-mask image** Calculate the histogram only within a mask image. - **-ignorezero** ignore zero-valued data during histogram construction. diff --git a/docs/reference/commands/mrhistogram.rst b/docs/reference/commands/mrhistogram.rst index ebffc404c5..a26eb8590a 100644 --- a/docs/reference/commands/mrhistogram.rst +++ b/docs/reference/commands/mrhistogram.rst @@ -26,6 +26,8 @@ Histogram generation options - **-bins num** Manually set the number of bins to use to generate the histogram. +- **-template file** Use an existing histogram file as the template for histogram formation + - **-mask image** Calculate the histogram only within a mask image. - **-ignorezero** ignore zero-valued data during histogram construction. diff --git a/lib/algo/histogram.cpp b/lib/algo/histogram.cpp index ad47678407..9278ba179c 100644 --- a/lib/algo/histogram.cpp +++ b/lib/algo/histogram.cpp @@ -30,6 +30,9 @@ namespace MR + Option ("bins", "Manually set the number of bins to use to generate the histogram.") + Argument ("num").type_integer (2) + + Option ("template", "Use an existing histogram file as the template for histogram formation") + + Argument ("file").type_file_in() + + Option ("mask", "Calculate the histogram only within a mask image.") + Argument ("image").type_image_in() @@ -38,6 +41,31 @@ namespace MR + void Calibrator::from_file (const std::string& path) + { + Eigen::MatrixXd M; + try { + M = load_matrix (path); + if (M.cols() == 1) + throw Exception ("Histogram template must have at least 2 columns"); + std::vector().swap (data); + auto V = M.row(0); + num_bins = V.size(); + bin_width = (V[num_bins-1] - V[0]) / default_type(num_bins-1); + min = V[0] - (0.5 * bin_width); + max = V[num_bins-1] + (0.5 * bin_width); + for (size_t i = 0; i != num_bins; ++i) { + if (std::abs (get_bin_centre(i) - V[i]) > 1e-5) + throw Exception ("Non-equal spacing in histogram bin centres"); + } + } catch (Exception& e) { + throw Exception (e, "Could not use file \"" + path + "\" as histogram template"); + } + + } + + + void Calibrator::finalize (const size_t num_volumes, const bool is_integer) { if (!std::isfinite (bin_width)) { diff --git a/lib/algo/histogram.h b/lib/algo/histogram.h index 28554ef73d..b7929af8ee 100644 --- a/lib/algo/histogram.h +++ b/lib/algo/histogram.h @@ -54,8 +54,15 @@ namespace MR return true; } + void from_file (const std::string&); + void finalize (const size_t num_volumes, const bool is_integer); + default_type get_bin_centre (const size_t i) const { + assert (i < num_bins); + return get_min() + (get_bin_width() * (i + 0.5)); + } + default_type get_bin_width() const { return bin_width; } size_t get_num_bins() const { return num_bins; } default_type get_min() const { return min; } @@ -113,9 +120,6 @@ namespace MR size_t size() const { return list.size(); } - default_type get_bin_centre (const size_t i) const { - return info.get_min() + (info.get_bin_width() * (i + 0.5)); - } const Calibrator& get_calibration() const { return info; } const vector_type& pdf() const { return list; } From f2795a783bc70678a9a996114fc1d44ac4adc817 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 27 Jul 2016 19:01:16 +1000 Subject: [PATCH 042/723] NIfTI2: Initial support Current implementation is that the NIfTI / Analyse formats seamlessly read / write either NIfTI1.1 or NIfTI2. The disadvantage of this approach is that mrinfo is unable to report whether a particular file is NIfTI version 1.1 or 2. Therefore the code will likely be split in order to provide separate NIfTI2 formats. --- cmd/transformcalc.cpp | 2 +- cmd/transformconvert.cpp | 2 +- lib/file/mgh_utils.cpp | 4 +- lib/file/nifti2.h | 125 ++++++ .../{nifti1_utils.cpp => nifti_utils.cpp} | 390 +++++++++++++++++- lib/file/{nifti1_utils.h => nifti_utils.h} | 15 +- lib/formats/analyse.cpp | 45 +- lib/formats/list.h | 6 +- lib/formats/nifti.cpp | 118 ++++++ lib/formats/nifti1.cpp | 88 ---- lib/formats/nifti1_gz.cpp | 94 ----- lib/formats/nifti_gz.cpp | 131 ++++++ src/dwi/gradient.cpp | 2 +- 13 files changed, 808 insertions(+), 214 deletions(-) create mode 100644 lib/file/nifti2.h rename lib/file/{nifti1_utils.cpp => nifti_utils.cpp} (53%) rename lib/file/{nifti1_utils.h => nifti_utils.h} (63%) create mode 100644 lib/formats/nifti.cpp delete mode 100644 lib/formats/nifti1.cpp delete mode 100644 lib/formats/nifti1_gz.cpp create mode 100644 lib/formats/nifti_gz.cpp diff --git a/cmd/transformcalc.cpp b/cmd/transformcalc.cpp index eeea7ea9cf..2627703f39 100644 --- a/cmd/transformcalc.cpp +++ b/cmd/transformcalc.cpp @@ -20,7 +20,7 @@ #include "math/math.h" #include "math/average_space.h" #include "image.h" -#include "file/nifti1_utils.h" +#include "file/nifti_utils.h" #include "transform.h" #include "file/key_value.h" diff --git a/cmd/transformconvert.cpp b/cmd/transformconvert.cpp index 9a72f669d4..55e5184a8b 100644 --- a/cmd/transformconvert.cpp +++ b/cmd/transformconvert.cpp @@ -19,7 +19,7 @@ #include "command.h" #include "math/math.h" #include "image.h" -#include "file/nifti1_utils.h" +#include "file/nifti_utils.h" #include "transform.h" #include "file/key_value.h" diff --git a/lib/file/mgh_utils.cpp b/lib/file/mgh_utils.cpp index 1eecebac8e..13196124e0 100644 --- a/lib/file/mgh_utils.cpp +++ b/lib/file/mgh_utils.cpp @@ -15,9 +15,9 @@ #include "header.h" #include "raw.h" -#include "file/ofstream.h" #include "file/mgh_utils.h" -#include "file/nifti1_utils.h" +#include "file/nifti_utils.h" +#include "file/ofstream.h" namespace MR { diff --git a/lib/file/nifti2.h b/lib/file/nifti2.h new file mode 100644 index 0000000000..0315d32f0c --- /dev/null +++ b/lib/file/nifti2.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +/** \file nifti2.h + \brief Header structure for NIFTI-2 format. + */ + +#ifndef __NIFTI2_HEADER +#define __NIFTI2_HEADER + +/*---------------------------------------------------------------------------*/ +/* Changes to the header from NIFTI-1 to NIFTI-2 are intended to allow for + larger and more accurate fields. The changes are as follows: + + - short dim[8] -> int64_t dim[8] + - float intent_p1,2,3 -> double intent_p1,2,3 (3 fields) + - float pixdim[8] -> double pixdim[8] + - float vox_offset -> int64_t vox_offset + - float scl_slope -> double scl_slope + - float scl_inter -> double scl_inter + - float cal_max -> double cal_max + - float cal_min -> double cal_min + - float slice_duration -> double slice_duration + - float toffset -> double toffset + - short slice_start -> int64_t slice_start + - short slice_end -> int64_t slice_end + - char slice_code -> int32_t slice_code + - char xyzt_units -> int32_t xyzt_units + - short intent_code -> int32_t intent_code + - short qform_code -> int32_t qform_code + - short sform_code -> int32_t sform_code + - float quatern_b,c,d -> double quatern_b,c,d (3 fields) + - float srow_x,y,z[4] -> double srow_x,y,z[4] (3 fields) + - char magic[4] -> char magic[8] + - char unused_str[15] -> padding added at the end of the header + + - previously unused fields have been removed: + data_type, db_name, extents, session_error, regular, glmax, glmin + + - the field ordering has been changed +-----------------------------------------------------------------------------*/ + +#include + +/*=================*/ +#ifdef __cplusplus +extern "C" { +#endif +/*=================*/ + +/*! \struct nifti_2_header + \brief Data structure defining the fields in the nifti2 header. + This binary header should be found at the beginning of a valid + NIFTI-2 header file. + */ + +/* hopefully cross-platform solution to byte padding added by some compilers */ +#pragma pack(push) +#pragma pack(1) + + /***************************/ /**********************/ /************/ +struct nifti_2_header { /* NIFTI-2 usage */ /* NIFTI-1 usage */ /* offset */ + /***************************/ /**********************/ /************/ + int sizeof_hdr; /*!< MUST be 540 */ /* int sizeof_hdr; (348) */ /* 0 */ + char magic[8] ; /*!< MUST be valid signature. */ /* char magic[4]; */ /* 4 */ + int16_t datatype; /*!< Defines data type! */ /* short datatype; */ /* 12 */ + int16_t bitpix; /*!< Number bits/voxel. */ /* short bitpix; */ /* 14 */ + int64_t dim[8]; /*!< Data array dimensions.*/ /* short dim[8]; */ /* 16 */ + double intent_p1 ; /*!< 1st intent parameter. */ /* float intent_p1; */ /* 80 */ + double intent_p2 ; /*!< 2nd intent parameter. */ /* float intent_p2; */ /* 88 */ + double intent_p3 ; /*!< 3rd intent parameter. */ /* float intent_p3; */ /* 96 */ + double pixdim[8]; /*!< Grid spacings. */ /* float pixdim[8]; */ /* 104 */ + int64_t vox_offset; /*!< Offset into .nii file */ /* float vox_offset; */ /* 168 */ + double scl_slope ; /*!< Data scaling: slope. */ /* float scl_slope; */ /* 176 */ + double scl_inter ; /*!< Data scaling: offset. */ /* float scl_inter; */ /* 184 */ + double cal_max; /*!< Max display intensity */ /* float cal_max; */ /* 192 */ + double cal_min; /*!< Min display intensity */ /* float cal_min; */ /* 200 */ + double slice_duration;/*!< Time for 1 slice. */ /* float slice_duration; */ /* 208 */ + double toffset; /*!< Time axis shift. */ /* float toffset; */ /* 216 */ + int64_t slice_start; /*!< First slice index. */ /* short slice_start; */ /* 224 */ + int64_t slice_end; /*!< Last slice index. */ /* short slice_end; */ /* 232 */ + char descrip[80]; /*!< any text you like. */ /* char descrip[80]; */ /* 240 */ + char aux_file[24]; /*!< auxiliary filename. */ /* char aux_file[24]; */ /* 320 */ + int qform_code ; /*!< NIFTI_XFORM_* code. */ /* short qform_code; */ /* 344 */ + int sform_code ; /*!< NIFTI_XFORM_* code. */ /* short sform_code; */ /* 348 */ + double quatern_b ; /*!< Quaternion b param. */ /* float quatern_b; */ /* 352 */ + double quatern_c ; /*!< Quaternion c param. */ /* float quatern_c; */ /* 360 */ + double quatern_d ; /*!< Quaternion d param. */ /* float quatern_d; */ /* 368 */ + double qoffset_x ; /*!< Quaternion x shift. */ /* float qoffset_x; */ /* 376 */ + double qoffset_y ; /*!< Quaternion y shift. */ /* float qoffset_y; */ /* 384 */ + double qoffset_z ; /*!< Quaternion z shift. */ /* float qoffset_z; */ /* 392 */ + double srow_x[4] ; /*!< 1st row affine transform. */ /* float srow_x[4]; */ /* 400 */ + double srow_y[4] ; /*!< 2nd row affine transform. */ /* float srow_y[4]; */ /* 432 */ + double srow_z[4] ; /*!< 3rd row affine transform. */ /* float srow_z[4]; */ /* 464 */ + int slice_code ; /*!< Slice timing order. */ /* char slice_code; */ /* 496 */ + int xyzt_units ; /*!< Units of pixdim[1..4] */ /* char xyzt_units; */ /* 500 */ + int intent_code ; /*!< NIFTI_INTENT_* code. */ /* short intent_code; */ /* 504 */ + char intent_name[16]; /*!< 'name' or meaning of data. */ /* char intent_name[16]; */ /* 508 */ + char dim_info; /*!< MRI slice ordering. */ /* char dim_info; */ /* 524 */ + char unused_str[15]; /*!< unused, filled with \0 */ /* 525 */ +} ; /**** 540 bytes total ****/ +typedef struct nifti_2_header nifti_2_header ; + +/* restore packing behavior */ +#pragma pack(pop) + +/*=================*/ +#ifdef __cplusplus +} +#endif +/*=================*/ + +#endif /* __NIFTI2_HEADER */ diff --git a/lib/file/nifti1_utils.cpp b/lib/file/nifti_utils.cpp similarity index 53% rename from lib/file/nifti1_utils.cpp rename to lib/file/nifti_utils.cpp index a5439167a2..cbe1d481e8 100644 --- a/lib/file/nifti1_utils.cpp +++ b/lib/file/nifti_utils.cpp @@ -16,7 +16,7 @@ #include "header.h" #include "raw.h" #include "file/config.h" -#include "file/nifti1_utils.h" +#include "file/nifti_utils.h" namespace MR { @@ -74,10 +74,12 @@ namespace MR size_t read (Header& H, const nifti_1_header& NH) { bool is_BE = false; - if (Raw::fetch_ (&NH.sizeof_hdr, is_BE) != 348) { + if (Raw::fetch_ (&NH.sizeof_hdr, is_BE) != ver1_hdr_size) { is_BE = true; - if (Raw::fetch_ (&NH.sizeof_hdr, is_BE) != 348) - throw Exception ("image \"" + H.name() + "\" is not in NIfTI format (sizeof_hdr != 348)"); + if (Raw::fetch_ (&NH.sizeof_hdr, is_BE) != ver1_hdr_size) { + std::cerr << "GDB break here\n"; + throw Exception ("image \"" + H.name() + "\" is not in NIfTI format (sizeof_hdr != " + str(ver1_hdr_size) + ")"); + } } bool is_nifti = true; @@ -156,7 +158,7 @@ namespace MR dtype = DataType::CFloat64; break; default: - throw Exception ("unknown data type for Analyse image \"" + H.name() + "\""); + throw Exception ("unknown data type for NIfTI image \"" + H.name() + "\""); } if (! (dtype.is (DataType::Bit) || dtype.is (DataType::UInt8) || dtype.is (DataType::Int8))) { @@ -278,6 +280,190 @@ namespace MR + size_t read (Header& H, const nifti_2_header& NH) + { + bool is_BE = false; + if (Raw::fetch_ (&NH.sizeof_hdr, is_BE) != ver2_hdr_size) { + is_BE = true; + if (Raw::fetch_ (&NH.sizeof_hdr, is_BE) != ver2_hdr_size) + throw Exception ("image \"" + H.name() + "\" is not in NIfTI-2 format (sizeof_hdr != " + str(ver2_hdr_size) + ")"); + } + + if (memcmp (NH.magic, "n+2\0", 4) && memcmp (NH.magic, "ni2\0", 4)) + throw Exception ("image \"" + H.name() + "\" is not in NIfTI-2 format (invalid magic signature)"); + + if (memcmp (NH.magic+4, ver2_sig_extra, 4)) + WARN ("possible file transfer corruption of file \"" + H.name() + "\" (invalid magic signature)"); + + // data type: + DataType dtype; + switch (Raw::fetch_ (&NH.datatype, is_BE)) { + case DT_BINARY: + dtype = DataType::Bit; + break; + case DT_INT8: + dtype = DataType::Int8; + break; + case DT_UINT8: + dtype = DataType::UInt8; + break; + case DT_INT16: + dtype = DataType::Int16; + break; + case DT_UINT16: + dtype = DataType::UInt16; + break; + case DT_INT32: + dtype = DataType::Int32; + break; + case DT_UINT32: + dtype = DataType::UInt32; + break; + case DT_INT64: + dtype = DataType::Int64; + break; + case DT_UINT64: + dtype = DataType::UInt64; + break; + case DT_FLOAT32: + dtype = DataType::Float32; + break; + case DT_FLOAT64: + dtype = DataType::Float64; + break; + case DT_COMPLEX64: + dtype = DataType::CFloat32; + break; + case DT_COMPLEX128: + dtype = DataType::CFloat64; + break; + default: + throw Exception ("unknown data type for NIfTI-2 image \"" + H.name() + "\""); + } + + if (! (dtype.is (DataType::Bit) || dtype.is (DataType::UInt8) || dtype.is (DataType::Int8))) { + if (is_BE) + dtype.set_flag (DataType::BigEndian); + else + dtype.set_flag (DataType::LittleEndian); + } + + H.datatype() = dtype; + + if (Raw::fetch_ (&NH.bitpix, is_BE) != (int16_t) dtype.bits()) + WARN ("bitpix field does not match data type in NIfTI-2 image \"" + H.name() + "\" - ignored"); + + // data set dimensions: + const int64_t ndim = Raw::fetch_ (&NH.dim, is_BE); + if (ndim < 1) + throw Exception ("too few dimensions specified in NIfTI-2 image \"" + H.name() + "\""); + if (ndim > 7) + throw Exception ("too many dimensions specified in NIfTI-2 image \"" + H.name() + "\""); + H.ndim() = ndim; + for (int64_t i = 0; i != ndim; i++) { + H.size(i) = Raw::fetch_ (&NH.dim[i+1], is_BE); + if (H.size (i) < 0) { + INFO ("dimension along axis " + str (i) + " specified as negative in NIfTI-2 image \"" + H.name() + "\" - taking absolute value"); + H.size(i) = std::abs (H.size (i)); + } + if (!H.size (i)) + H.size(i) = 1; + H.stride(i) = i+1; + } + + // voxel sizes: + for (int i = 0; i < ndim; i++) { + H.spacing(i) = Raw::fetch_ (&NH.pixdim[i+1], is_BE); + if (H.spacing (i) < 0.0) { + INFO ("voxel size along axis " + str (i) + " specified as negative in NIfTI-2 image \"" + H.name() + "\" - taking absolute value"); + H.spacing(i) = std::abs (H.spacing (i)); + } + } + + const int64_t data_offset = Raw::fetch_ (&NH.vox_offset, is_BE); + + // offset and scale: + H.intensity_scale() = Raw::fetch_ (&NH.scl_slope, is_BE); + if (std::isfinite (H.intensity_scale()) && H.intensity_scale() != 0.0) { + H.intensity_offset() = Raw::fetch_ (&NH.scl_inter, is_BE); + if (!std::isfinite (H.intensity_offset())) + H.intensity_offset() = 0.0; + } else { + H.reset_intensity_scaling(); + } + + char descrip[81]; + strncpy (descrip, NH.descrip, 80); + if (descrip[0]) { + descrip[80] = '\0'; + if (strncmp (descrip, "MRtrix version: ", 16) == 0) + H.keyval()["mrtrix_version"] = descrip+16; + else + add_line (H.keyval()["comments"], descrip); + } + + // Note: Unlike reading from a nifti_1_header class, here we + // don't have to worry about whether or not the file is in + // Analyse format; we can treat it as a NIfTI regardless of + // whether the hedaer & data are in the same file or not. + if (Raw::fetch_ (&NH.sform_code, is_BE)) { + auto& M (H.transform().matrix()); + + M(0,0) = Raw::fetch_ (&NH.srow_x[0], is_BE); + M(0,1) = Raw::fetch_ (&NH.srow_x[1], is_BE); + M(0,2) = Raw::fetch_ (&NH.srow_x[2], is_BE); + M(0,3) = Raw::fetch_ (&NH.srow_x[3], is_BE); + + M(1,0) = Raw::fetch_ (&NH.srow_y[0], is_BE); + M(1,1) = Raw::fetch_ (&NH.srow_y[1], is_BE); + M(1,2) = Raw::fetch_ (&NH.srow_y[2], is_BE); + M(1,3) = Raw::fetch_ (&NH.srow_y[3], is_BE); + + M(2,0) = Raw::fetch_ (&NH.srow_z[0], is_BE); + M(2,1) = Raw::fetch_ (&NH.srow_z[1], is_BE); + M(2,2) = Raw::fetch_ (&NH.srow_z[2], is_BE); + M(2,3) = Raw::fetch_ (&NH.srow_z[3], is_BE); + + // get voxel sizes: + H.spacing(0) = std::sqrt (Math::pow2 (M(0,0)) + Math::pow2 (M(1,0)) + Math::pow2 (M(2,0))); + H.spacing(1) = std::sqrt (Math::pow2 (M(0,1)) + Math::pow2 (M(1,1)) + Math::pow2 (M(2,1))); + H.spacing(2) = std::sqrt (Math::pow2 (M(0,2)) + Math::pow2 (M(1,2)) + Math::pow2 (M(2,2))); + + // normalize each transform axis: + M (0,0) /= H.spacing (0); + M (1,0) /= H.spacing (0); + M (2,0) /= H.spacing (0); + + M (0,1) /= H.spacing (1); + M (1,1) /= H.spacing (1); + M (2,1) /= H.spacing (1); + + M (0,2) /= H.spacing (2); + M (1,2) /= H.spacing (2); + M (2,2) /= H.spacing (2); + + } else if (Raw::fetch_ (&NH.qform_code, is_BE)) { + { // TODO update with Eigen3 Quaternions + Eigen::Quaterniond Q (0.0, Raw::fetch_ (&NH.quatern_b, is_BE), Raw::fetch_ (&NH.quatern_c, is_BE), Raw::fetch_ (&NH.quatern_d, is_BE)); + Q.w() = std::sqrt (std::max (1.0 - Q.squaredNorm(), 0.0)); + H.transform().matrix().topLeftCorner<3,3>() = Q.matrix(); + } + + H.transform().translation()[0] = Raw::fetch_ (&NH.qoffset_x, is_BE); + H.transform().translation()[1] = Raw::fetch_ (&NH.qoffset_y, is_BE); + H.transform().translation()[2] = Raw::fetch_ (&NH.qoffset_z, is_BE); + + // qfac: + const float64 qfac = Raw::fetch_ (&NH.pixdim[0], is_BE) >= 0.0 ? 1.0 : -1.0; + if (qfac < 0.0) + H.transform().matrix().col(2) *= qfac; + } + + return data_offset; + } + + + @@ -332,6 +518,35 @@ namespace MR + size_t version (Header& H) + { + //CONF option: NIFTI.AlwaysUseVer2 + //CONF default: 0 (false) + //CONF A boolean value to indicate whether NIfTI images should + //CONF always be written in the new NIfTI-2 format. If false, + //CONF images will be written in the older NIfTI-1 format by + //CONF default, with the exception being files where the number + //CONF of voxels along any axis exceeds the maximum permissible + //CONF in that format (32767), in which case the output file + //CONF will automatically switch to the NIfTI-2 format. + if (File::Config::get_bool ("NIFTI.AlwaysUseVer2", false)) + return 2; + + for (size_t axis = 0; axis != H.ndim(); ++axis) { + if (H.size(axis) > std::numeric_limits::max()) { + INFO ("Forcing file \"" + H.name() + "\" to use NIfTI version 2 due to image dimensions"); + return 2; + } + } + + return 1; + } + + + + + + @@ -351,7 +566,7 @@ namespace MR memset (&NH, 0, sizeof (NH)); // magic number: - Raw::store (348, &NH.sizeof_hdr, is_BE); + Raw::store (ver1_hdr_size, &NH.sizeof_hdr, is_BE); const auto hit = H.keyval().find("comments"); auto comments = split_lines (hit == H.keyval().end() ? std::string() : hit->second); @@ -434,15 +649,15 @@ namespace MR Raw::store (dt, &NH.datatype, is_BE); Raw::store (H.datatype().bits(), &NH.bitpix, is_BE); - NH.pixdim[0] = 1.0; + Raw::store (1.0, &NH.pixdim[0], is_BE); // voxel sizes: for (size_t i = 0; i < 3; ++i) Raw::store (H.spacing (axes[i]), &NH.pixdim[i+1], is_BE); for (size_t i = 3; i < H.ndim(); ++i) Raw::store (H.spacing (i), &NH.pixdim[i+1], is_BE); - Raw::store (352.0, &NH.vox_offset, is_BE); + Raw::store (float32(ver1_hdr_with_ext_size), &NH.vox_offset, is_BE); // offset and scale: Raw::store (H.intensity_scale(), &NH.scl_slope, is_BE); @@ -463,7 +678,7 @@ namespace MR Eigen::Matrix3d R = M.matrix().topLeftCorner<3,3>(); if (R.determinant() < 0.0) { R.col(2) = -R.col(2); - NH.pixdim[0] = -1.0; + Raw::store (-1.0, &NH.pixdim[0], is_BE); } Eigen::Quaterniond Q (R); @@ -501,6 +716,163 @@ namespace MR + void write (nifti_2_header& NH, const Header& H, const bool has_nii_suffix) + { + if (H.ndim() > 7) + throw Exception ("NIfTI-2 format cannot support more than 7 dimensions for image \"" + H.name() + "\""); + + bool is_BE = H.datatype().is_big_endian(); + + std::vector axes; + auto M = adjust_transform (H, axes); + + + memset (&NH, 0, sizeof (NH)); + + // magic number: + Raw::store (ver2_hdr_size, &NH.sizeof_hdr, is_BE); + + strncpy ( (char*) &NH.magic, has_nii_suffix ? "n+2\0" : "ni2\0", 4); + strncpy ( (char*) &NH.magic+4, ver2_sig_extra, 4); + + // data type: + int16_t dt = 0; + switch (H.datatype()()) { + case DataType::Bit: + dt = DT_BINARY; + break; + case DataType::Int8: + dt = DT_INT8; + break; + case DataType::UInt8: + dt = DT_UINT8; + break; + case DataType::Int16LE: + case DataType::Int16BE: + dt = DT_INT16; + break; + case DataType::UInt16LE: + case DataType::UInt16BE: + dt = DT_UINT16; + break; + case DataType::Int32LE: + case DataType::Int32BE: + dt = DT_INT32; + break; + case DataType::UInt32LE: + case DataType::UInt32BE: + dt = DT_UINT32; + break; + case DataType::Int64LE: + case DataType::Int64BE: + dt = DT_INT64; + break; + case DataType::UInt64LE: + case DataType::UInt64BE: + dt = DT_UINT64; + break; + case DataType::Float32LE: + case DataType::Float32BE: + dt = DT_FLOAT32; + break; + case DataType::Float64LE: + case DataType::Float64BE: + dt = DT_FLOAT64; + break; + case DataType::CFloat32LE: + case DataType::CFloat32BE: + dt = DT_COMPLEX64; + break; + case DataType::CFloat64LE: + case DataType::CFloat64BE: + dt = DT_COMPLEX128; + break; + default: + throw Exception ("unknown data type for NIfTI-2 image \"" + H.name() + "\""); + } + Raw::store (dt, &NH.datatype, is_BE); + + Raw::store (H.datatype().bits(), &NH.bitpix, is_BE); + + // data set dimensions: + Raw::store (H.ndim(), &NH.dim[0], is_BE); + { + size_t i = 0; + for (; i < 3; i++) + Raw::store (H.size (axes[i]), &NH.dim[i+1], is_BE); + for (; i < H.ndim(); i++) + Raw::store (H.size (i), &NH.dim[i+1], is_BE); + + // pad out the other dimensions with 1, fix for fslview + ++i; + for (; i < 8; i++) + Raw::store (1, &NH.dim[i], is_BE); + } + + Raw::store (1.0, &NH.pixdim[0], is_BE); + // voxel sizes: + for (size_t i = 0; i < 3; ++i) + Raw::store (H.spacing (axes[i]), &NH.pixdim[i+1], is_BE); + for (size_t i = 3; i < H.ndim(); ++i) + Raw::store (H.spacing (i), &NH.pixdim[i+1], is_BE); + + Raw::store (int64_t(ver2_hdr_with_ext_size), &NH.vox_offset, is_BE); + + // offset and scale: + Raw::store (H.intensity_scale(), &NH.scl_slope, is_BE); + Raw::store (H.intensity_offset(), &NH.scl_inter, is_BE); + + std::string version_string = std::string("MRtrix version: ") + App::mrtrix_version; + if (App::project_version) + version_string += std::string(", project version: ") + App::project_version; + strncpy ( (char*) &NH.descrip, version_string.c_str(), 79); + + Raw::store (NIFTI_XFORM_SCANNER_ANAT, &NH.qform_code, is_BE); + Raw::store (NIFTI_XFORM_SCANNER_ANAT, &NH.sform_code, is_BE); + + // qform: + Eigen::Matrix3d R = M.matrix().topLeftCorner<3,3>(); + if (R.determinant() < 0.0) { + R.col(2) = -R.col(2); + Raw::store (-1.0, &NH.pixdim[0], is_BE); + } + Eigen::Quaterniond Q (R); + + if (Q.w() < 0.0) + Q.vec() = -Q.vec(); + + Raw::store (Q.x(), &NH.quatern_b, is_BE); + Raw::store (Q.y(), &NH.quatern_c, is_BE); + Raw::store (Q.z(), &NH.quatern_d, is_BE); + + Raw::store (M(0,3), &NH.qoffset_x, is_BE); + Raw::store (M(1,3), &NH.qoffset_y, is_BE); + Raw::store (M(2,3), &NH.qoffset_z, is_BE); + + // sform: + Raw::store (H.spacing (axes[0]) * M(0,0), &NH.srow_x[0], is_BE); + Raw::store (H.spacing (axes[1]) * M(0,1), &NH.srow_x[1], is_BE); + Raw::store (H.spacing (axes[2]) * M(0,2), &NH.srow_x[2], is_BE); + Raw::store (M (0,3), &NH.srow_x[3], is_BE); + + Raw::store (H.spacing (axes[0]) * M(1,0), &NH.srow_y[0], is_BE); + Raw::store (H.spacing (axes[1]) * M(1,1), &NH.srow_y[1], is_BE); + Raw::store (H.spacing (axes[2]) * M(1,2), &NH.srow_y[2], is_BE); + Raw::store (M (1,3), &NH.srow_y[3], is_BE); + + Raw::store (H.spacing (axes[0]) * M(2,0), &NH.srow_z[0], is_BE); + Raw::store (H.spacing (axes[1]) * M(2,1), &NH.srow_z[1], is_BE); + Raw::store (H.spacing (axes[2]) * M(2,2), &NH.srow_z[2], is_BE); + Raw::store (M (2,3), &NH.srow_z[3], is_BE); + + // TODO Test to see whether this is indeed endian-flipped + const char xyzt_units[4] { NIFTI_UNITS_MM, NIFTI_UNITS_MM, NIFTI_UNITS_MM, NIFTI_UNITS_SEC }; + const int32_t* const xyzt_units_as_int_ptr = reinterpret_cast(xyzt_units); + Raw::store (*xyzt_units_as_int_ptr, &NH.xyzt_units, is_BE); + } + + + } } } diff --git a/lib/file/nifti1_utils.h b/lib/file/nifti_utils.h similarity index 63% rename from lib/file/nifti1_utils.h rename to lib/file/nifti_utils.h index 498fb63e4a..0843d4008c 100644 --- a/lib/file/nifti1_utils.h +++ b/lib/file/nifti_utils.h @@ -13,10 +13,11 @@ * */ -#ifndef __file_nifti1_utils_h__ -#define __file_nifti1_utils_h__ +#ifndef __file_nifti_utils_h__ +#define __file_nifti_utils_h__ #include "file/nifti1.h" +#include "file/nifti2.h" namespace MR { @@ -27,12 +28,20 @@ namespace MR namespace NIfTI { + constexpr size_t ver1_hdr_size = 348; + constexpr size_t ver1_hdr_with_ext_size = 352; + constexpr size_t ver2_hdr_size = 540; + constexpr size_t ver2_hdr_with_ext_size = 544; + constexpr char ver2_sig_extra[4] { '\r', '\n', '\032', '\n' }; + transform_type adjust_transform (const Header& H, std::vector& order); - void check (Header& H, bool single_file); size_t read (Header& H, const nifti_1_header& NH); + size_t read (Header& H, const nifti_2_header& NH); void check (Header& H, bool single_file); + size_t version (Header& H); void write (nifti_1_header& NH, const Header& H, bool single_file); + void write (nifti_2_header& NH, const Header& H, bool single_file); } } diff --git a/lib/formats/analyse.cpp b/lib/formats/analyse.cpp index 59472ef24f..82f3ce9d5a 100644 --- a/lib/formats/analyse.cpp +++ b/lib/formats/analyse.cpp @@ -16,7 +16,7 @@ #include "file/ofstream.h" #include "file/utils.h" #include "file/entry.h" -#include "file/nifti1_utils.h" +#include "file/nifti_utils.h" #include "header.h" #include "formats/list.h" #include "image_io/default.h" @@ -31,8 +31,14 @@ namespace MR if (!Path::has_suffix (H.name(), ".img")) return std::unique_ptr(); - File::MMap fmap (H.name().substr (0, H.name().size()-4) + ".hdr"); - File::NIfTI::read (H, * ( (const nifti_1_header*) fmap.address())); + const std::string header_path = H.name().substr (0, H.name().size()-4) + ".hdr"; + File::MMap fmap (header_path); + if (fmap.size() == File::NIfTI::ver1_hdr_size || fmap.size() == File::NIfTI::ver1_hdr_with_ext_size) + File::NIfTI::read (H, * ( (const nifti_1_header*) fmap.address())); + else if (fmap.size() == File::NIfTI::ver2_hdr_size || fmap.size() == File::NIfTI::ver2_hdr_with_ext_size) + File::NIfTI::read (H, * ( (const nifti_2_header*) fmap.address())); + else + throw Exception ("Error reading NIfTI header file \"" + header_path + "\": Invalid size (" + str(fmap.size()) + ")"); std::unique_ptr io_handler (new ImageIO::Default (H)); io_handler->files.push_back (File::Entry (H.name())); @@ -50,10 +56,10 @@ namespace MR return false; if (num_axes < 3) - throw Exception ("cannot create NIfTI-1.1 image with less than 3 dimensions"); + throw Exception ("cannot create NIfTI image with less than 3 dimensions"); - if (num_axes > 8) - throw Exception ("cannot create NIfTI-1.1 image with more than 8 dimensions"); + if (num_axes > 7) + throw Exception ("cannot create NIfTI image with more than 7 dimensions"); H.ndim() = num_axes; File::NIfTI::check (H, false); @@ -68,14 +74,29 @@ namespace MR std::unique_ptr Analyse::create (Header& H) const { if (H.ndim() > 7) - throw Exception ("NIfTI-1.1 format cannot support more than 7 dimensions for image \"" + H.name() + "\""); + throw Exception ("NIfTI format cannot support more than 7 dimensions for image \"" + H.name() + "\""); - nifti_1_header NH; - File::NIfTI::write (NH, H, false); - - std::string hdr_name (H.name().substr (0, H.name().size()-4) + ".hdr"); + const std::string hdr_name (H.name().substr (0, H.name().size()-4) + ".hdr"); File::OFStream out (hdr_name); - out.write ( (char*) &NH, 352); + + const size_t nifti_version = File::NIfTI::version (H); + switch (nifti_version) { + case 1: { + nifti_1_header NH; + File::NIfTI::write (NH, H, false); + out.write ( (char*) &NH, sizeof (nifti_1_header)); + } + break; + case 2: { + nifti_2_header NH; + File::NIfTI::write (NH, H, false); + out.write ( (char*) &NH, sizeof (nifti_2_header)); + } + break; + default: + throw Exception ("Error determining NIfTI version for file \"" + H.name() + "\""); + } + out.close(); File::create (H.name(), footprint(H)); diff --git a/lib/formats/list.h b/lib/formats/list.h index 6abc8e2e28..88eeb458ae 100644 --- a/lib/formats/list.h +++ b/lib/formats/list.h @@ -93,9 +93,9 @@ namespace MR DECLARE_IMAGEFORMAT (DICOM, "DICOM"); DECLARE_IMAGEFORMAT (MRtrix, "MRtrix"); DECLARE_IMAGEFORMAT (MRtrix_GZ, "MRtrix (GZip compressed)"); - DECLARE_IMAGEFORMAT (NIfTI, "NIfTI-1.1"); - DECLARE_IMAGEFORMAT (NIfTI_GZ, "NIfTI-1.1 (GZip compressed)"); - DECLARE_IMAGEFORMAT (Analyse, "AnalyseAVW / NIfTI-1.1"); + DECLARE_IMAGEFORMAT (NIfTI, "NIfTI"); + DECLARE_IMAGEFORMAT (NIfTI_GZ, "NIfTI (GZip compressed)"); + DECLARE_IMAGEFORMAT (Analyse, "AnalyseAVW / NIfTI"); DECLARE_IMAGEFORMAT (MRI, "MRTools (legacy format)"); DECLARE_IMAGEFORMAT (XDS, "XDS"); DECLARE_IMAGEFORMAT (MGH, "MGH"); diff --git a/lib/formats/nifti.cpp b/lib/formats/nifti.cpp new file mode 100644 index 0000000000..d3ad5d3e8a --- /dev/null +++ b/lib/formats/nifti.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "file/ofstream.h" +#include "file/path.h" +#include "file/utils.h" +#include "file/nifti_utils.h" +#include "header.h" +#include "image_io/default.h" +#include "formats/list.h" + +namespace MR +{ + namespace Formats + { + + std::unique_ptr NIfTI::read (Header& H) const + { + if (!Path::has_suffix (H.name(), ".nii")) + return std::unique_ptr(); + + File::MMap fmap (H.name()); + size_t data_offset = 0; + try { + data_offset = File::NIfTI::read (H, * ( (const nifti_1_header*) fmap.address())); + } catch (...) { + try { + data_offset = File::NIfTI::read (H, * ( (const nifti_2_header*) fmap.address())); + } catch (...) { + throw Exception ("Error opening NIfti file \"" + H.name() + "\": Unsupported version"); + } + } + + std::unique_ptr handler (new ImageIO::Default (H)); + handler->files.push_back (File::Entry (H.name(), data_offset)); + + return std::move (handler); + } + + + + + + bool NIfTI::check (Header& H, size_t num_axes) const + { + if (!Path::has_suffix (H.name(), ".nii")) return (false); + if (num_axes < 3) throw Exception ("cannot create NIfTI image with less than 3 dimensions"); + if (num_axes > 7) throw Exception ("cannot create NIfTI image with more than 7 dimensions"); + + H.ndim() = num_axes; + File::NIfTI::check (H, true); + + return true; + } + + + + + + std::unique_ptr NIfTI::create (Header& H) const + { + if (H.ndim() > 7) + throw Exception ("NIfTI format cannot support more than 7 dimensions for image \"" + H.name() + "\""); + + const size_t nifti_version = File::NIfTI::version (H); + size_t data_offset = 0; + File::OFStream out (H.name(), std::ios::out | std::ios::binary); + switch (nifti_version) { + case 1: { + nifti_1_header NH; + File::NIfTI::write (NH, H, true); + out.write ( (char*) &NH, sizeof (nifti_1_header)); + data_offset = File::NIfTI::ver1_hdr_with_ext_size; + DEBUG ("Image \"" + H.name() + "\" being created with NIfTI version 1.1"); + } + break; + case 2: { + nifti_2_header NH; + File::NIfTI::write (NH, H, true); + out.write ( (char*) &NH, sizeof (nifti_2_header)); + data_offset = File::NIfTI::ver2_hdr_with_ext_size; + DEBUG ("Image \"" + H.name() + "\" being created with NIfTI version 2"); + } + break; + default: + throw Exception ("Error determining NIfTI version for file \"" + H.name() + "\""); + } + + nifti1_extender extender; + memset (extender.extension, 0x00, sizeof (nifti1_extender)); + out.write (extender.extension, sizeof (nifti1_extender)); + out.close(); + + File::resize (H.name(), data_offset + footprint(H)); + + std::unique_ptr handler (new ImageIO::Default (H)); + handler->files.push_back (File::Entry (H.name(), data_offset)); + + return std::move (handler); + } + + + + } +} + diff --git a/lib/formats/nifti1.cpp b/lib/formats/nifti1.cpp deleted file mode 100644 index 2933d1f8cd..0000000000 --- a/lib/formats/nifti1.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - -#include "file/ofstream.h" -#include "file/path.h" -#include "file/utils.h" -#include "file/nifti1_utils.h" -#include "header.h" -#include "image_io/default.h" -#include "formats/list.h" - -namespace MR -{ - namespace Formats - { - - std::unique_ptr NIfTI::read (Header& H) const - { - if (!Path::has_suffix (H.name(), ".nii")) - return std::unique_ptr(); - - File::MMap fmap (H.name()); - size_t data_offset = File::NIfTI::read (H, * ( (const nifti_1_header*) fmap.address())); - - std::unique_ptr handler (new ImageIO::Default (H)); - handler->files.push_back (File::Entry (H.name(), data_offset)); - - return std::move (handler); - } - - - - - - bool NIfTI::check (Header& H, size_t num_axes) const - { - if (!Path::has_suffix (H.name(), ".nii")) return (false); - if (num_axes < 3) throw Exception ("cannot create NIfTI-1.1 image with less than 3 dimensions"); - if (num_axes > 8) throw Exception ("cannot create NIfTI-1.1 image with more than 8 dimensions"); - - H.ndim() = num_axes; - File::NIfTI::check (H, true); - - return true; - } - - - - - - std::unique_ptr NIfTI::create (Header& H) const - { - if (H.ndim() > 7) - throw Exception ("NIfTI-1.1 format cannot support more than 7 dimensions for image \"" + H.name() + "\""); - - nifti_1_header NH; - nifti1_extender extender; - memset (extender.extension, 0x00, sizeof (nifti1_extender)); - File::NIfTI::write (NH, H, true); - - File::OFStream out (H.name(), std::ios::out | std::ios::binary); - out.write ( (char*) &NH, 348); - out.write (extender.extension, 4); - out.close(); - - File::resize (H.name(), 352 + footprint(H)); - - std::unique_ptr handler (new ImageIO::Default (H)); - handler->files.push_back (File::Entry (H.name(), 352)); - - return std::move (handler); - } - - } -} - diff --git a/lib/formats/nifti1_gz.cpp b/lib/formats/nifti1_gz.cpp deleted file mode 100644 index 1b98859e14..0000000000 --- a/lib/formats/nifti1_gz.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - -#include "file/utils.h" -#include "file/path.h" -#include "file/gz.h" -#include "file/nifti1_utils.h" -#include "header.h" -#include "image_io/gz.h" -#include "formats/list.h" - -namespace MR -{ - namespace Formats - { - - - std::unique_ptr NIfTI_GZ::read (Header& H) const - { - if (!Path::has_suffix (H.name(), ".nii.gz")) - return std::unique_ptr(); - - nifti_1_header NH; - - File::GZ zf (H.name(), "rb"); - zf.read (reinterpret_cast (&NH), sizeof (nifti_1_header)); - zf.close(); - - size_t data_offset = File::NIfTI::read (H, NH); - - std::unique_ptr io_handler (new ImageIO::GZ (H, sizeof(nifti_1_header)+sizeof(nifti1_extender))); - memcpy (io_handler.get()->header(), &NH, sizeof(nifti_1_header)); - memset (io_handler.get()->header() + sizeof(nifti_1_header), 0, sizeof(nifti1_extender)); - io_handler->files.push_back (File::Entry (H.name(), data_offset)); - - return std::move (io_handler); - } - - - - - - bool NIfTI_GZ::check (Header& H, size_t num_axes) const - { - if (!Path::has_suffix (H.name(), ".nii.gz")) - return false; - - if (num_axes < 3) - throw Exception ("cannot create NIfTI-1.1 image with less than 3 dimensions"); - - if (num_axes > 8) - throw Exception ("cannot create NIfTI-1.1 image with more than 8 dimensions"); - - H.ndim() = num_axes; - File::NIfTI::check (H, true); - - return true; - } - - - - - - std::unique_ptr NIfTI_GZ::create (Header& H) const - { - if (H.ndim() > 7) - throw Exception ("NIfTI-1.1 format cannot support more than 7 dimensions for image \"" + H.name() + "\""); - - std::unique_ptr io_handler (new ImageIO::GZ (H, sizeof(nifti_1_header)+sizeof(nifti1_extender))); - - File::NIfTI::write (*reinterpret_cast (io_handler->header()), H, true); - memset (io_handler->header()+sizeof(nifti_1_header), 0, sizeof(nifti1_extender)); - - File::create (H.name()); - io_handler->files.push_back (File::Entry (H.name(), sizeof(nifti_1_header)+sizeof(nifti1_extender))); - - return std::move (io_handler); - } - - } -} - diff --git a/lib/formats/nifti_gz.cpp b/lib/formats/nifti_gz.cpp new file mode 100644 index 0000000000..b5e540f432 --- /dev/null +++ b/lib/formats/nifti_gz.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "file/utils.h" +#include "file/path.h" +#include "file/gz.h" +#include "file/nifti_utils.h" +#include "header.h" +#include "image_io/gz.h" +#include "formats/list.h" + +namespace MR +{ + namespace Formats + { + + + std::unique_ptr NIfTI_GZ::read (Header& H) const + { + if (!Path::has_suffix (H.name(), ".nii.gz")) + return std::unique_ptr(); + + nifti_1_header N1H; + nifti_2_header N2H; + size_t data_offset = 0; + + try { + File::GZ zf (H.name(), "rb"); + zf.read (reinterpret_cast (&N1H), File::NIfTI::ver1_hdr_size); + zf.close(); + data_offset = File::NIfTI::read (H, N1H); + } catch (...) { + try { + File::GZ zf (H.name(), "rb"); + zf.read (reinterpret_cast (&N2H), File::NIfTI::ver2_hdr_size); + zf.close(); + data_offset = File::NIfTI::read (H, N2H); + } catch (...) { + throw Exception ("Error opening NIfti file \"" + H.name() + "\": Unsupported version"); + } + } + + std::unique_ptr io_handler (new ImageIO::GZ (H, data_offset)); + if (data_offset == File::NIfTI::ver1_hdr_with_ext_size) { + memcpy (io_handler.get()->header(), &N1H, File::NIfTI::ver1_hdr_size); + memset (io_handler.get()->header() + File::NIfTI::ver1_hdr_size, 0, sizeof(nifti1_extender)); + } else { + memcpy (io_handler.get()->header(), &N2H, File::NIfTI::ver2_hdr_size); + memset (io_handler.get()->header() + File::NIfTI::ver2_hdr_size, 0, sizeof(nifti1_extender)); + } + io_handler->files.push_back (File::Entry (H.name(), data_offset)); + + return std::move (io_handler); + } + + + + + + bool NIfTI_GZ::check (Header& H, size_t num_axes) const + { + if (!Path::has_suffix (H.name(), ".nii.gz")) + return false; + + if (num_axes < 3) + throw Exception ("cannot create NIfTI image with less than 3 dimensions"); + + if (num_axes > 7) + throw Exception ("cannot create NIfTI image with more than 7 dimensions"); + + H.ndim() = num_axes; + File::NIfTI::check (H, true); + + return true; + } + + + + + + std::unique_ptr NIfTI_GZ::create (Header& H) const + { + if (H.ndim() > 7) + throw Exception ("NIfTI format cannot support more than 7 dimensions for image \"" + H.name() + "\""); + + const size_t nifti_version = File::NIfTI::version (H); + size_t data_offset = 0; + switch (nifti_version) { + case 1: data_offset = File::NIfTI::ver1_hdr_with_ext_size; break; + case 2: data_offset = File::NIfTI::ver2_hdr_with_ext_size; break; + default: throw Exception ("Error determining NIfTI version for file \"" + H.name() + "\""); + } + + std::unique_ptr io_handler (new ImageIO::GZ (H, data_offset)); + + switch (nifti_version) { + case 1: + File::NIfTI::write (*reinterpret_cast (io_handler->header()), H, true); + memset (io_handler->header()+File::NIfTI::ver1_hdr_size, 0, sizeof(nifti1_extender)); + break; + case 2: + File::NIfTI::write (*reinterpret_cast (io_handler->header()), H, true); + memset (io_handler->header()+File::NIfTI::ver2_hdr_size, 0, sizeof(nifti1_extender)); + break; + default: + break; + } + + File::create (H.name()); + io_handler->files.push_back (File::Entry (H.name(), data_offset)); + + return std::move (io_handler); + } + + + + } +} + diff --git a/src/dwi/gradient.cpp b/src/dwi/gradient.cpp index 08b24d81ef..801d104b4c 100644 --- a/src/dwi/gradient.cpp +++ b/src/dwi/gradient.cpp @@ -14,7 +14,7 @@ */ #include "dwi/gradient.h" -#include "file/nifti1_utils.h" +#include "file/nifti_utils.h" namespace MR { From 108635b518d916cff0c60f752cb0c3158c9e4edf Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 27 Jul 2016 22:22:24 +1000 Subject: [PATCH 043/723] NIfTI2 support: Changed structure NIfTI-2 and GZip-compressed NIfTI-2 files are now dealt with using dedicated handlers, such that the underlying fomat is visible using mrinfo. The un-compressed NIfTI-2 format is used for both .nii files and .img/.hdr pairs whenever version 2 is invoked. --- lib/file/nifti1_utils.cpp | 408 +++++++++++++++++++++ lib/file/nifti1_utils.h | 41 +++ lib/file/nifti2_utils.cpp | 378 +++++++++++++++++++ lib/file/nifti2_utils.h | 42 +++ lib/file/nifti_utils.cpp | 739 +------------------------------------- lib/file/nifti_utils.h | 17 +- lib/formats/analyse.cpp | 58 +-- lib/formats/list.cpp | 12 +- lib/formats/list.h | 6 +- lib/formats/nifti.cpp | 118 ------ lib/formats/nifti1.cpp | 94 +++++ lib/formats/nifti1_gz.cpp | 95 +++++ lib/formats/nifti2.cpp | 110 ++++++ lib/formats/nifti2_gz.cpp | 93 +++++ lib/formats/nifti_gz.cpp | 131 ------- 15 files changed, 1298 insertions(+), 1044 deletions(-) create mode 100644 lib/file/nifti1_utils.cpp create mode 100644 lib/file/nifti1_utils.h create mode 100644 lib/file/nifti2_utils.cpp create mode 100644 lib/file/nifti2_utils.h delete mode 100644 lib/formats/nifti.cpp create mode 100644 lib/formats/nifti1.cpp create mode 100644 lib/formats/nifti1_gz.cpp create mode 100644 lib/formats/nifti2.cpp create mode 100644 lib/formats/nifti2_gz.cpp delete mode 100644 lib/formats/nifti_gz.cpp diff --git a/lib/file/nifti1_utils.cpp b/lib/file/nifti1_utils.cpp new file mode 100644 index 0000000000..66aabfc2e6 --- /dev/null +++ b/lib/file/nifti1_utils.cpp @@ -0,0 +1,408 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "header.h" +#include "raw.h" +#include "file/config.h" +#include "file/nifti_utils.h" +#include "file/nifti1_utils.h" + +namespace MR +{ + namespace File + { + namespace NIfTI1 + { + + + + size_t read (Header& H, const nifti_1_header& NH) + { + bool is_BE = false; + if (Raw::fetch_ (&NH.sizeof_hdr, is_BE) != header_size) { + is_BE = true; + if (Raw::fetch_ (&NH.sizeof_hdr, is_BE) != header_size) + throw Exception ("image \"" + H.name() + "\" is not in NIfTI-1.1 format (sizeof_hdr != " + str(header_size) + ")"); + } + + bool is_nifti = true; + if (memcmp (NH.magic, "n+1\0", 4) && memcmp (NH.magic, "ni1\0", 4)) { + is_nifti = false; + DEBUG ("assuming image \"" + H.name() + "\" is in AnalyseAVW format."); + } + + char db_name[19]; + strncpy (db_name, NH.db_name, 18); + if (db_name[0]) { + db_name[18] = '\0'; + add_line (H.keyval()["comments"], db_name); + } + + // data set dimensions: + int ndim = Raw::fetch_ (&NH.dim, is_BE); + if (ndim < 1) + throw Exception ("too few dimensions specified in NIfTI-1.1 image \"" + H.name() + "\""); + if (ndim > 7) + throw Exception ("too many dimensions specified in NIfTI-1.1 image \"" + H.name() + "\""); + H.ndim() = ndim; + + + for (int i = 0; i < ndim; i++) { + H.size(i) = Raw::fetch_ (&NH.dim[i+1], is_BE); + if (H.size (i) < 0) { + INFO ("dimension along axis " + str (i) + " specified as negative in NIfTI-1.1 image \"" + H.name() + "\" - taking absolute value"); + H.size(i) = std::abs (H.size (i)); + } + if (!H.size (i)) + H.size(i) = 1; + H.stride(i) = i+1; + } + + // data type: + DataType dtype; + switch (Raw::fetch_ (&NH.datatype, is_BE)) { + case DT_BINARY: + dtype = DataType::Bit; + break; + case DT_INT8: + dtype = DataType::Int8; + break; + case DT_UINT8: + dtype = DataType::UInt8; + break; + case DT_INT16: + dtype = DataType::Int16; + break; + case DT_UINT16: + dtype = DataType::UInt16; + break; + case DT_INT32: + dtype = DataType::Int32; + break; + case DT_UINT32: + dtype = DataType::UInt32; + break; + case DT_INT64: + dtype = DataType::Int64; + break; + case DT_UINT64: + dtype = DataType::UInt64; + break; + case DT_FLOAT32: + dtype = DataType::Float32; + break; + case DT_FLOAT64: + dtype = DataType::Float64; + break; + case DT_COMPLEX64: + dtype = DataType::CFloat32; + break; + case DT_COMPLEX128: + dtype = DataType::CFloat64; + break; + default: + throw Exception ("unknown data type for NIfTI-1.1 image \"" + H.name() + "\""); + } + + if (! (dtype.is (DataType::Bit) || dtype.is (DataType::UInt8) || dtype.is (DataType::Int8))) { + if (is_BE) + dtype.set_flag (DataType::BigEndian); + else + dtype.set_flag (DataType::LittleEndian); + } + + if (Raw::fetch_ (&NH.bitpix, is_BE) != (int16_t) dtype.bits()) + WARN ("bitpix field does not match data type in NIfTI-1.1 image \"" + H.name() + "\" - ignored"); + + H.datatype() = dtype; + + // voxel sizes: + for (int i = 0; i < ndim; i++) { + H.spacing(i) = Raw::fetch_ (&NH.pixdim[i+1], is_BE); + if (H.spacing (i) < 0.0) { + INFO ("voxel size along axis " + str (i) + " specified as negative in NIfTI-1.1 image \"" + H.name() + "\" - taking absolute value"); + H.spacing(i) = std::abs (H.spacing (i)); + } + } + + + // offset and scale: + H.intensity_scale() = Raw::fetch_ (&NH.scl_slope, is_BE); + if (std::isfinite (H.intensity_scale()) && H.intensity_scale() != 0.0) { + H.intensity_offset() = Raw::fetch_ (&NH.scl_inter, is_BE); + if (!std::isfinite (H.intensity_offset())) + H.intensity_offset() = 0.0; + } + else + H.reset_intensity_scaling(); + + size_t data_offset = (size_t) Raw::fetch_ (&NH.vox_offset, is_BE); + + char descrip[81]; + strncpy (descrip, NH.descrip, 80); + if (descrip[0]) { + descrip[80] = '\0'; + if (strncmp (descrip, "MRtrix version: ", 16) == 0) + H.keyval()["mrtrix_version"] = descrip+16; + else + add_line (H.keyval()["comments"], descrip); + } + + if (is_nifti) { + if (Raw::fetch_ (&NH.sform_code, is_BE)) { + auto& M (H.transform().matrix()); + + M(0,0) = Raw::fetch_ (&NH.srow_x[0], is_BE); + M(0,1) = Raw::fetch_ (&NH.srow_x[1], is_BE); + M(0,2) = Raw::fetch_ (&NH.srow_x[2], is_BE); + M(0,3) = Raw::fetch_ (&NH.srow_x[3], is_BE); + + M(1,0) = Raw::fetch_ (&NH.srow_y[0], is_BE); + M(1,1) = Raw::fetch_ (&NH.srow_y[1], is_BE); + M(1,2) = Raw::fetch_ (&NH.srow_y[2], is_BE); + M(1,3) = Raw::fetch_ (&NH.srow_y[3], is_BE); + + M(2,0) = Raw::fetch_ (&NH.srow_z[0], is_BE); + M(2,1) = Raw::fetch_ (&NH.srow_z[1], is_BE); + M(2,2) = Raw::fetch_ (&NH.srow_z[2], is_BE); + M(2,3) = Raw::fetch_ (&NH.srow_z[3], is_BE); + + // get voxel sizes: + H.spacing(0) = std::sqrt (Math::pow2 (M(0,0)) + Math::pow2 (M(1,0)) + Math::pow2 (M(2,0))); + H.spacing(1) = std::sqrt (Math::pow2 (M(0,1)) + Math::pow2 (M(1,1)) + Math::pow2 (M(2,1))); + H.spacing(2) = std::sqrt (Math::pow2 (M(0,2)) + Math::pow2 (M(1,2)) + Math::pow2 (M(2,2))); + + // normalize each transform axis: + M (0,0) /= H.spacing (0); + M (1,0) /= H.spacing (0); + M (2,0) /= H.spacing (0); + + M (0,1) /= H.spacing (1); + M (1,1) /= H.spacing (1); + M (2,1) /= H.spacing (1); + + M (0,2) /= H.spacing (2); + M (1,2) /= H.spacing (2); + M (2,2) /= H.spacing (2); + + } + else if (Raw::fetch_ (&NH.qform_code, is_BE)) { + { // TODO update with Eigen3 Quaternions + Eigen::Quaterniond Q (0.0, Raw::fetch_ (&NH.quatern_b, is_BE), Raw::fetch_ (&NH.quatern_c, is_BE), Raw::fetch_ (&NH.quatern_d, is_BE)); + Q.w() = std::sqrt (std::max (1.0 - Q.squaredNorm(), 0.0)); + H.transform().matrix().topLeftCorner<3,3>() = Q.matrix(); + } + + H.transform().translation()[0] = Raw::fetch_ (&NH.qoffset_x, is_BE); + H.transform().translation()[1] = Raw::fetch_ (&NH.qoffset_y, is_BE); + H.transform().translation()[2] = Raw::fetch_ (&NH.qoffset_z, is_BE); + + // qfac: + float qfac = Raw::fetch_ (&NH.pixdim[0], is_BE) >= 0.0 ? 1.0 : -1.0; + if (qfac < 0.0) + H.transform().matrix().col(2) *= qfac; + } + } + else { + H.transform()(0,0) = std::numeric_limits::quiet_NaN(); + //CONF option: AnalyseLeftToRight + //CONF default: 0 (false) + //CONF A boolean value to indicate whether images in Analyse format + //CONF should be assumed to be in LAS orientation (default) or RAS + //CONF (when this is option is turned on). + if (!File::Config::get_bool ("AnalyseLeftToRight", false)) + H.stride(0) = -H.stride (0); + if (!File::NIfTI::right_left_warning_issued) { + INFO ("assuming Analyse images are encoded " + std::string (H.stride (0) > 0 ? "left to right" : "right to left")); + File::NIfTI::right_left_warning_issued = true; + } + } + + return data_offset; + } + + + + + + + + void write (nifti_1_header& NH, const Header& H, const bool single_file) + { + if (H.ndim() > 7) + throw Exception ("NIfTI-1.1 format cannot support more than 7 dimensions for image \"" + H.name() + "\""); + + bool is_BE = H.datatype().is_big_endian(); + + std::vector axes; + auto M = File::NIfTI::adjust_transform (H, axes); + + memset (&NH, 0, sizeof (NH)); + + // magic number: + Raw::store (header_size, &NH.sizeof_hdr, is_BE); + + const auto hit = H.keyval().find("comments"); + auto comments = split_lines (hit == H.keyval().end() ? std::string() : hit->second); + strncpy ( (char*) &NH.db_name, comments.size() ? comments[0].c_str() : "untitled\0\0\0\0\0\0\0\0\0\0\0", 18); + Raw::store (16384, &NH.extents, is_BE); + NH.regular = 'r'; + NH.dim_info = 0; + + // data set dimensions: + Raw::store (H.ndim(), &NH.dim[0], is_BE); + { + size_t i = 0; + for (; i < 3; i++) + Raw::store (H.size (axes[i]), &NH.dim[i+1], is_BE); + for (; i < H.ndim(); i++) + Raw::store (H.size (i), &NH.dim[i+1], is_BE); + + // pad out the other dimensions with 1, fix for fslview + ++i; + for (; i < 8; i++) + Raw::store (1, &NH.dim[i], is_BE); + } + + + // data type: + int16_t dt = 0; + switch (H.datatype()()) { + case DataType::Bit: + dt = DT_BINARY; + break; + case DataType::Int8: + dt = DT_INT8; + break; + case DataType::UInt8: + dt = DT_UINT8; + break; + case DataType::Int16LE: + case DataType::Int16BE: + dt = DT_INT16; + break; + case DataType::UInt16LE: + case DataType::UInt16BE: + dt = DT_UINT16; + break; + case DataType::Int32LE: + case DataType::Int32BE: + dt = DT_INT32; + break; + case DataType::UInt32LE: + case DataType::UInt32BE: + dt = DT_UINT32; + break; + case DataType::Int64LE: + case DataType::Int64BE: + dt = DT_INT64; + break; + case DataType::UInt64LE: + case DataType::UInt64BE: + dt = DT_UINT64; + break; + case DataType::Float32LE: + case DataType::Float32BE: + dt = DT_FLOAT32; + break; + case DataType::Float64LE: + case DataType::Float64BE: + dt = DT_FLOAT64; + break; + case DataType::CFloat32LE: + case DataType::CFloat32BE: + dt = DT_COMPLEX64; + break; + case DataType::CFloat64LE: + case DataType::CFloat64BE: + dt = DT_COMPLEX128; + break; + default: + throw Exception ("unknown data type for NIfTI-1.1 image \"" + H.name() + "\""); + } + + Raw::store (dt, &NH.datatype, is_BE); + Raw::store (H.datatype().bits(), &NH.bitpix, is_BE); + + Raw::store (1.0, &NH.pixdim[0], is_BE); + // voxel sizes: + for (size_t i = 0; i < 3; ++i) + Raw::store (H.spacing (axes[i]), &NH.pixdim[i+1], is_BE); + for (size_t i = 3; i < H.ndim(); ++i) + Raw::store (H.spacing (i), &NH.pixdim[i+1], is_BE); + + Raw::store (float32(header_with_ext_size), &NH.vox_offset, is_BE); + + // offset and scale: + Raw::store (H.intensity_scale(), &NH.scl_slope, is_BE); + Raw::store (H.intensity_offset(), &NH.scl_inter, is_BE); + + NH.xyzt_units = SPACE_TIME_TO_XYZT (NIFTI_UNITS_MM, NIFTI_UNITS_SEC); + + memset ((char*) &NH.descrip, 0, 80); + std::string version_string = std::string("MRtrix version: ") + App::mrtrix_version; + if (App::project_version) + version_string += std::string(", project version: ") + App::project_version; + strncpy ( (char*) &NH.descrip, version_string.c_str(), 79); + + Raw::store (NIFTI_XFORM_SCANNER_ANAT, &NH.qform_code, is_BE); + Raw::store (NIFTI_XFORM_SCANNER_ANAT, &NH.sform_code, is_BE); + + // qform: + Eigen::Matrix3d R = M.matrix().topLeftCorner<3,3>(); + if (R.determinant() < 0.0) { + R.col(2) = -R.col(2); + Raw::store (-1.0, &NH.pixdim[0], is_BE); + } + Eigen::Quaterniond Q (R); + + if (Q.w() < 0.0) + Q.vec() = -Q.vec(); + + Raw::store (Q.x(), &NH.quatern_b, is_BE); + Raw::store (Q.y(), &NH.quatern_c, is_BE); + Raw::store (Q.z(), &NH.quatern_d, is_BE); + + + // sform: + + Raw::store (M(0,3), &NH.qoffset_x, is_BE); + Raw::store (M(1,3), &NH.qoffset_y, is_BE); + Raw::store (M(2,3), &NH.qoffset_z, is_BE); + + Raw::store (H.spacing (axes[0]) * M(0,0), &NH.srow_x[0], is_BE); + Raw::store (H.spacing (axes[1]) * M(0,1), &NH.srow_x[1], is_BE); + Raw::store (H.spacing (axes[2]) * M(0,2), &NH.srow_x[2], is_BE); + Raw::store (M (0,3), &NH.srow_x[3], is_BE); + + Raw::store (H.spacing (axes[0]) * M(1,0), &NH.srow_y[0], is_BE); + Raw::store (H.spacing (axes[1]) * M(1,1), &NH.srow_y[1], is_BE); + Raw::store (H.spacing (axes[2]) * M(1,2), &NH.srow_y[2], is_BE); + Raw::store (M (1,3), &NH.srow_y[3], is_BE); + + Raw::store (H.spacing (axes[0]) * M(2,0), &NH.srow_z[0], is_BE); + Raw::store (H.spacing (axes[1]) * M(2,1), &NH.srow_z[1], is_BE); + Raw::store (H.spacing (axes[2]) * M(2,2), &NH.srow_z[2], is_BE); + Raw::store (M (2,3), &NH.srow_z[3], is_BE); + + strncpy ( (char*) &NH.magic, single_file ? "n+1\0" : "ni1\0", 4); + } + + + + } + } +} + diff --git a/lib/file/nifti1_utils.h b/lib/file/nifti1_utils.h new file mode 100644 index 0000000000..338383af98 --- /dev/null +++ b/lib/file/nifti1_utils.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __file_nifti1_utils_h__ +#define __file_nifti1_utils_h__ + +#include "file/nifti1.h" + +namespace MR +{ + class Header; + + namespace File + { + namespace NIfTI1 + { + + constexpr size_t header_size = 348; + constexpr size_t header_with_ext_size = 352; + + size_t read (Header& H, const nifti_1_header& NH); + void write (nifti_1_header& NH, const Header& H, const bool single_file); + + } + } +} + +#endif + diff --git a/lib/file/nifti2_utils.cpp b/lib/file/nifti2_utils.cpp new file mode 100644 index 0000000000..f5ec2256e3 --- /dev/null +++ b/lib/file/nifti2_utils.cpp @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "header.h" +#include "raw.h" +#include "file/config.h" +#include "file/nifti1.h" +#include "file/nifti_utils.h" +#include "file/nifti2_utils.h" + +namespace MR +{ + namespace File + { + namespace NIfTI2 + { + + + + size_t read (Header& H, const nifti_2_header& NH) + { + bool is_BE = false; + if (Raw::fetch_ (&NH.sizeof_hdr, is_BE) != header_size) { + is_BE = true; + if (Raw::fetch_ (&NH.sizeof_hdr, is_BE) != header_size) + throw Exception ("image \"" + H.name() + "\" is not in NIfTI-2 format (sizeof_hdr != " + str(header_size) + ")"); + } + + if (memcmp (NH.magic, "n+2\0", 4) && memcmp (NH.magic, "ni2\0", 4)) + throw Exception ("image \"" + H.name() + "\" is not in NIfTI-2 format (invalid magic signature)"); + + if (memcmp (NH.magic+4, signature_extra, 4)) + WARN ("possible file transfer corruption of file \"" + H.name() + "\" (invalid magic signature)"); + + // data type: + DataType dtype; + switch (Raw::fetch_ (&NH.datatype, is_BE)) { + case DT_BINARY: + dtype = DataType::Bit; + break; + case DT_INT8: + dtype = DataType::Int8; + break; + case DT_UINT8: + dtype = DataType::UInt8; + break; + case DT_INT16: + dtype = DataType::Int16; + break; + case DT_UINT16: + dtype = DataType::UInt16; + break; + case DT_INT32: + dtype = DataType::Int32; + break; + case DT_UINT32: + dtype = DataType::UInt32; + break; + case DT_INT64: + dtype = DataType::Int64; + break; + case DT_UINT64: + dtype = DataType::UInt64; + break; + case DT_FLOAT32: + dtype = DataType::Float32; + break; + case DT_FLOAT64: + dtype = DataType::Float64; + break; + case DT_COMPLEX64: + dtype = DataType::CFloat32; + break; + case DT_COMPLEX128: + dtype = DataType::CFloat64; + break; + default: + throw Exception ("unknown data type for NIfTI-2 image \"" + H.name() + "\""); + } + + if (! (dtype.is (DataType::Bit) || dtype.is (DataType::UInt8) || dtype.is (DataType::Int8))) { + if (is_BE) + dtype.set_flag (DataType::BigEndian); + else + dtype.set_flag (DataType::LittleEndian); + } + + H.datatype() = dtype; + + if (Raw::fetch_ (&NH.bitpix, is_BE) != (int16_t) dtype.bits()) + WARN ("bitpix field does not match data type in NIfTI-2 image \"" + H.name() + "\" - ignored"); + + // data set dimensions: + const int64_t ndim = Raw::fetch_ (&NH.dim, is_BE); + if (ndim < 1) + throw Exception ("too few dimensions specified in NIfTI-2 image \"" + H.name() + "\""); + if (ndim > 7) + throw Exception ("too many dimensions specified in NIfTI-2 image \"" + H.name() + "\""); + H.ndim() = ndim; + for (int64_t i = 0; i != ndim; i++) { + H.size(i) = Raw::fetch_ (&NH.dim[i+1], is_BE); + if (H.size (i) < 0) { + INFO ("dimension along axis " + str (i) + " specified as negative in NIfTI-2 image \"" + H.name() + "\" - taking absolute value"); + H.size(i) = std::abs (H.size (i)); + } + if (!H.size (i)) + H.size(i) = 1; + H.stride(i) = i+1; + } + + // voxel sizes: + for (int i = 0; i < ndim; i++) { + H.spacing(i) = Raw::fetch_ (&NH.pixdim[i+1], is_BE); + if (H.spacing (i) < 0.0) { + INFO ("voxel size along axis " + str (i) + " specified as negative in NIfTI-2 image \"" + H.name() + "\" - taking absolute value"); + H.spacing(i) = std::abs (H.spacing (i)); + } + } + + const int64_t data_offset = Raw::fetch_ (&NH.vox_offset, is_BE); + + // offset and scale: + H.intensity_scale() = Raw::fetch_ (&NH.scl_slope, is_BE); + if (std::isfinite (H.intensity_scale()) && H.intensity_scale() != 0.0) { + H.intensity_offset() = Raw::fetch_ (&NH.scl_inter, is_BE); + if (!std::isfinite (H.intensity_offset())) + H.intensity_offset() = 0.0; + } else { + H.reset_intensity_scaling(); + } + + char descrip[81]; + strncpy (descrip, NH.descrip, 80); + if (descrip[0]) { + descrip[80] = '\0'; + if (strncmp (descrip, "MRtrix version: ", 16) == 0) + H.keyval()["mrtrix_version"] = descrip+16; + else + add_line (H.keyval()["comments"], descrip); + } + + // Note: Unlike reading from a nifti_1_header class, here we + // don't have to worry about whether or not the file is in + // Analyse format; we can treat it as a NIfTI regardless of + // whether the hedaer & data are in the same file or not. + if (Raw::fetch_ (&NH.sform_code, is_BE)) { + auto& M (H.transform().matrix()); + + M(0,0) = Raw::fetch_ (&NH.srow_x[0], is_BE); + M(0,1) = Raw::fetch_ (&NH.srow_x[1], is_BE); + M(0,2) = Raw::fetch_ (&NH.srow_x[2], is_BE); + M(0,3) = Raw::fetch_ (&NH.srow_x[3], is_BE); + + M(1,0) = Raw::fetch_ (&NH.srow_y[0], is_BE); + M(1,1) = Raw::fetch_ (&NH.srow_y[1], is_BE); + M(1,2) = Raw::fetch_ (&NH.srow_y[2], is_BE); + M(1,3) = Raw::fetch_ (&NH.srow_y[3], is_BE); + + M(2,0) = Raw::fetch_ (&NH.srow_z[0], is_BE); + M(2,1) = Raw::fetch_ (&NH.srow_z[1], is_BE); + M(2,2) = Raw::fetch_ (&NH.srow_z[2], is_BE); + M(2,3) = Raw::fetch_ (&NH.srow_z[3], is_BE); + + // get voxel sizes: + H.spacing(0) = std::sqrt (Math::pow2 (M(0,0)) + Math::pow2 (M(1,0)) + Math::pow2 (M(2,0))); + H.spacing(1) = std::sqrt (Math::pow2 (M(0,1)) + Math::pow2 (M(1,1)) + Math::pow2 (M(2,1))); + H.spacing(2) = std::sqrt (Math::pow2 (M(0,2)) + Math::pow2 (M(1,2)) + Math::pow2 (M(2,2))); + + // normalize each transform axis: + M (0,0) /= H.spacing (0); + M (1,0) /= H.spacing (0); + M (2,0) /= H.spacing (0); + + M (0,1) /= H.spacing (1); + M (1,1) /= H.spacing (1); + M (2,1) /= H.spacing (1); + + M (0,2) /= H.spacing (2); + M (1,2) /= H.spacing (2); + M (2,2) /= H.spacing (2); + + } else if (Raw::fetch_ (&NH.qform_code, is_BE)) { + { // TODO update with Eigen3 Quaternions + Eigen::Quaterniond Q (0.0, Raw::fetch_ (&NH.quatern_b, is_BE), Raw::fetch_ (&NH.quatern_c, is_BE), Raw::fetch_ (&NH.quatern_d, is_BE)); + Q.w() = std::sqrt (std::max (1.0 - Q.squaredNorm(), 0.0)); + H.transform().matrix().topLeftCorner<3,3>() = Q.matrix(); + } + + H.transform().translation()[0] = Raw::fetch_ (&NH.qoffset_x, is_BE); + H.transform().translation()[1] = Raw::fetch_ (&NH.qoffset_y, is_BE); + H.transform().translation()[2] = Raw::fetch_ (&NH.qoffset_z, is_BE); + + // qfac: + const float64 qfac = Raw::fetch_ (&NH.pixdim[0], is_BE) >= 0.0 ? 1.0 : -1.0; + if (qfac < 0.0) + H.transform().matrix().col(2) *= qfac; + } + + return data_offset; + } + + + + + + + void write (nifti_2_header& NH, const Header& H, const bool single_file) + { + if (H.ndim() > 7) + throw Exception ("NIfTI-2 format cannot support more than 7 dimensions for image \"" + H.name() + "\""); + + bool is_BE = H.datatype().is_big_endian(); + + std::vector axes; + auto M = File::NIfTI::adjust_transform (H, axes); + + + memset (&NH, 0, sizeof (NH)); + + // magic number: + Raw::store (header_size, &NH.sizeof_hdr, is_BE); + + strncpy ( (char*) &NH.magic, single_file ? "n+2\0" : "ni2\0", 4); + strncpy ( (char*) &NH.magic+4, signature_extra, 4); + + // data type: + int16_t dt = 0; + switch (H.datatype()()) { + case DataType::Bit: + dt = DT_BINARY; + break; + case DataType::Int8: + dt = DT_INT8; + break; + case DataType::UInt8: + dt = DT_UINT8; + break; + case DataType::Int16LE: + case DataType::Int16BE: + dt = DT_INT16; + break; + case DataType::UInt16LE: + case DataType::UInt16BE: + dt = DT_UINT16; + break; + case DataType::Int32LE: + case DataType::Int32BE: + dt = DT_INT32; + break; + case DataType::UInt32LE: + case DataType::UInt32BE: + dt = DT_UINT32; + break; + case DataType::Int64LE: + case DataType::Int64BE: + dt = DT_INT64; + break; + case DataType::UInt64LE: + case DataType::UInt64BE: + dt = DT_UINT64; + break; + case DataType::Float32LE: + case DataType::Float32BE: + dt = DT_FLOAT32; + break; + case DataType::Float64LE: + case DataType::Float64BE: + dt = DT_FLOAT64; + break; + case DataType::CFloat32LE: + case DataType::CFloat32BE: + dt = DT_COMPLEX64; + break; + case DataType::CFloat64LE: + case DataType::CFloat64BE: + dt = DT_COMPLEX128; + break; + default: + throw Exception ("unknown data type for NIfTI-2 image \"" + H.name() + "\""); + } + Raw::store (dt, &NH.datatype, is_BE); + + Raw::store (H.datatype().bits(), &NH.bitpix, is_BE); + + // data set dimensions: + Raw::store (H.ndim(), &NH.dim[0], is_BE); + { + size_t i = 0; + for (; i < 3; i++) + Raw::store (H.size (axes[i]), &NH.dim[i+1], is_BE); + for (; i < H.ndim(); i++) + Raw::store (H.size (i), &NH.dim[i+1], is_BE); + + // pad out the other dimensions with 1, fix for fslview + ++i; + for (; i < 8; i++) + Raw::store (1, &NH.dim[i], is_BE); + } + + Raw::store (1.0, &NH.pixdim[0], is_BE); + // voxel sizes: + for (size_t i = 0; i < 3; ++i) + Raw::store (H.spacing (axes[i]), &NH.pixdim[i+1], is_BE); + for (size_t i = 3; i < H.ndim(); ++i) + Raw::store (H.spacing (i), &NH.pixdim[i+1], is_BE); + + Raw::store (int64_t(header_with_ext_size), &NH.vox_offset, is_BE); + + // offset and scale: + Raw::store (H.intensity_scale(), &NH.scl_slope, is_BE); + Raw::store (H.intensity_offset(), &NH.scl_inter, is_BE); + + std::string version_string = std::string("MRtrix version: ") + App::mrtrix_version; + if (App::project_version) + version_string += std::string(", project version: ") + App::project_version; + strncpy ( (char*) &NH.descrip, version_string.c_str(), 79); + + Raw::store (NIFTI_XFORM_SCANNER_ANAT, &NH.qform_code, is_BE); + Raw::store (NIFTI_XFORM_SCANNER_ANAT, &NH.sform_code, is_BE); + + // qform: + Eigen::Matrix3d R = M.matrix().topLeftCorner<3,3>(); + if (R.determinant() < 0.0) { + R.col(2) = -R.col(2); + Raw::store (-1.0, &NH.pixdim[0], is_BE); + } + Eigen::Quaterniond Q (R); + + if (Q.w() < 0.0) + Q.vec() = -Q.vec(); + + Raw::store (Q.x(), &NH.quatern_b, is_BE); + Raw::store (Q.y(), &NH.quatern_c, is_BE); + Raw::store (Q.z(), &NH.quatern_d, is_BE); + + Raw::store (M(0,3), &NH.qoffset_x, is_BE); + Raw::store (M(1,3), &NH.qoffset_y, is_BE); + Raw::store (M(2,3), &NH.qoffset_z, is_BE); + + // sform: + Raw::store (H.spacing (axes[0]) * M(0,0), &NH.srow_x[0], is_BE); + Raw::store (H.spacing (axes[1]) * M(0,1), &NH.srow_x[1], is_BE); + Raw::store (H.spacing (axes[2]) * M(0,2), &NH.srow_x[2], is_BE); + Raw::store (M (0,3), &NH.srow_x[3], is_BE); + + Raw::store (H.spacing (axes[0]) * M(1,0), &NH.srow_y[0], is_BE); + Raw::store (H.spacing (axes[1]) * M(1,1), &NH.srow_y[1], is_BE); + Raw::store (H.spacing (axes[2]) * M(1,2), &NH.srow_y[2], is_BE); + Raw::store (M (1,3), &NH.srow_y[3], is_BE); + + Raw::store (H.spacing (axes[0]) * M(2,0), &NH.srow_z[0], is_BE); + Raw::store (H.spacing (axes[1]) * M(2,1), &NH.srow_z[1], is_BE); + Raw::store (H.spacing (axes[2]) * M(2,2), &NH.srow_z[2], is_BE); + Raw::store (M (2,3), &NH.srow_z[3], is_BE); + + const char xyzt_units[4] { NIFTI_UNITS_MM, NIFTI_UNITS_MM, NIFTI_UNITS_MM, NIFTI_UNITS_SEC }; + const int32_t* const xyzt_units_as_int_ptr = reinterpret_cast(xyzt_units); + Raw::store (*xyzt_units_as_int_ptr, &NH.xyzt_units, is_BE); + } + + + + } + } +} + diff --git a/lib/file/nifti2_utils.h b/lib/file/nifti2_utils.h new file mode 100644 index 0000000000..57346ceb84 --- /dev/null +++ b/lib/file/nifti2_utils.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __file_nifti2_utils_h__ +#define __file_nifti2_utils_h__ + +#include "file/nifti2.h" + +namespace MR +{ + class Header; + + namespace File + { + namespace NIfTI2 + { + + constexpr size_t header_size = 540; + constexpr size_t header_with_ext_size = 544; + constexpr char signature_extra[4] { '\r', '\n', '\032', '\n' }; + + size_t read (Header& H, const nifti_2_header& NH); + void write (nifti_2_header& NH, const Header& H, const bool single_file); + + } + } +} + +#endif + diff --git a/lib/file/nifti_utils.cpp b/lib/file/nifti_utils.cpp index cbe1d481e8..f268902333 100644 --- a/lib/file/nifti_utils.cpp +++ b/lib/file/nifti_utils.cpp @@ -25,13 +25,6 @@ namespace MR namespace NIfTI { - namespace - { - bool right_left_warning_issued = false; - } - - - transform_type adjust_transform (const Header& H, std::vector& axes) @@ -69,406 +62,7 @@ namespace MR - - - size_t read (Header& H, const nifti_1_header& NH) - { - bool is_BE = false; - if (Raw::fetch_ (&NH.sizeof_hdr, is_BE) != ver1_hdr_size) { - is_BE = true; - if (Raw::fetch_ (&NH.sizeof_hdr, is_BE) != ver1_hdr_size) { - std::cerr << "GDB break here\n"; - throw Exception ("image \"" + H.name() + "\" is not in NIfTI format (sizeof_hdr != " + str(ver1_hdr_size) + ")"); - } - } - - bool is_nifti = true; - if (memcmp (NH.magic, "n+1\0", 4) && memcmp (NH.magic, "ni1\0", 4)) { - is_nifti = false; - DEBUG ("assuming image \"" + H.name() + "\" is in AnalyseAVW format."); - } - - char db_name[19]; - strncpy (db_name, NH.db_name, 18); - if (db_name[0]) { - db_name[18] = '\0'; - add_line (H.keyval()["comments"], db_name); - } - - // data set dimensions: - int ndim = Raw::fetch_ (&NH.dim, is_BE); - if (ndim < 1) - throw Exception ("too few dimensions specified in NIfTI image \"" + H.name() + "\""); - if (ndim > 7) - throw Exception ("too many dimensions specified in NIfTI image \"" + H.name() + "\""); - H.ndim() = ndim; - - - for (int i = 0; i < ndim; i++) { - H.size(i) = Raw::fetch_ (&NH.dim[i+1], is_BE); - if (H.size (i) < 0) { - INFO ("dimension along axis " + str (i) + " specified as negative in NIfTI image \"" + H.name() + "\" - taking absolute value"); - H.size(i) = std::abs (H.size (i)); - } - if (!H.size (i)) - H.size(i) = 1; - H.stride(i) = i+1; - } - - // data type: - DataType dtype; - switch (Raw::fetch_ (&NH.datatype, is_BE)) { - case DT_BINARY: - dtype = DataType::Bit; - break; - case DT_INT8: - dtype = DataType::Int8; - break; - case DT_UINT8: - dtype = DataType::UInt8; - break; - case DT_INT16: - dtype = DataType::Int16; - break; - case DT_UINT16: - dtype = DataType::UInt16; - break; - case DT_INT32: - dtype = DataType::Int32; - break; - case DT_UINT32: - dtype = DataType::UInt32; - break; - case DT_INT64: - dtype = DataType::Int64; - break; - case DT_UINT64: - dtype = DataType::UInt64; - break; - case DT_FLOAT32: - dtype = DataType::Float32; - break; - case DT_FLOAT64: - dtype = DataType::Float64; - break; - case DT_COMPLEX64: - dtype = DataType::CFloat32; - break; - case DT_COMPLEX128: - dtype = DataType::CFloat64; - break; - default: - throw Exception ("unknown data type for NIfTI image \"" + H.name() + "\""); - } - - if (! (dtype.is (DataType::Bit) || dtype.is (DataType::UInt8) || dtype.is (DataType::Int8))) { - if (is_BE) - dtype.set_flag (DataType::BigEndian); - else - dtype.set_flag (DataType::LittleEndian); - } - - if (Raw::fetch_ (&NH.bitpix, is_BE) != (int16_t) dtype.bits()) - WARN ("bitpix field does not match data type in NIfTI image \"" + H.name() + "\" - ignored"); - - H.datatype() = dtype; - - // voxel sizes: - for (int i = 0; i < ndim; i++) { - H.spacing(i) = Raw::fetch_ (&NH.pixdim[i+1], is_BE); - if (H.spacing (i) < 0.0) { - INFO ("voxel size along axis " + str (i) + " specified as negative in NIfTI image \"" + H.name() + "\" - taking absolute value"); - H.spacing(i) = std::abs (H.spacing (i)); - } - } - - - // offset and scale: - H.intensity_scale() = Raw::fetch_ (&NH.scl_slope, is_BE); - if (std::isfinite (H.intensity_scale()) && H.intensity_scale() != 0.0) { - H.intensity_offset() = Raw::fetch_ (&NH.scl_inter, is_BE); - if (!std::isfinite (H.intensity_offset())) - H.intensity_offset() = 0.0; - } - else - H.reset_intensity_scaling(); - - size_t data_offset = (size_t) Raw::fetch_ (&NH.vox_offset, is_BE); - - char descrip[81]; - strncpy (descrip, NH.descrip, 80); - if (descrip[0]) { - descrip[80] = '\0'; - if (strncmp (descrip, "MRtrix version: ", 16) == 0) - H.keyval()["mrtrix_version"] = descrip+16; - else - add_line (H.keyval()["comments"], descrip); - } - - if (is_nifti) { - if (Raw::fetch_ (&NH.sform_code, is_BE)) { - auto& M (H.transform().matrix()); - - M(0,0) = Raw::fetch_ (&NH.srow_x[0], is_BE); - M(0,1) = Raw::fetch_ (&NH.srow_x[1], is_BE); - M(0,2) = Raw::fetch_ (&NH.srow_x[2], is_BE); - M(0,3) = Raw::fetch_ (&NH.srow_x[3], is_BE); - - M(1,0) = Raw::fetch_ (&NH.srow_y[0], is_BE); - M(1,1) = Raw::fetch_ (&NH.srow_y[1], is_BE); - M(1,2) = Raw::fetch_ (&NH.srow_y[2], is_BE); - M(1,3) = Raw::fetch_ (&NH.srow_y[3], is_BE); - - M(2,0) = Raw::fetch_ (&NH.srow_z[0], is_BE); - M(2,1) = Raw::fetch_ (&NH.srow_z[1], is_BE); - M(2,2) = Raw::fetch_ (&NH.srow_z[2], is_BE); - M(2,3) = Raw::fetch_ (&NH.srow_z[3], is_BE); - - // get voxel sizes: - H.spacing(0) = std::sqrt (Math::pow2 (M(0,0)) + Math::pow2 (M(1,0)) + Math::pow2 (M(2,0))); - H.spacing(1) = std::sqrt (Math::pow2 (M(0,1)) + Math::pow2 (M(1,1)) + Math::pow2 (M(2,1))); - H.spacing(2) = std::sqrt (Math::pow2 (M(0,2)) + Math::pow2 (M(1,2)) + Math::pow2 (M(2,2))); - - // normalize each transform axis: - M (0,0) /= H.spacing (0); - M (1,0) /= H.spacing (0); - M (2,0) /= H.spacing (0); - - M (0,1) /= H.spacing (1); - M (1,1) /= H.spacing (1); - M (2,1) /= H.spacing (1); - - M (0,2) /= H.spacing (2); - M (1,2) /= H.spacing (2); - M (2,2) /= H.spacing (2); - - } - else if (Raw::fetch_ (&NH.qform_code, is_BE)) { - { // TODO update with Eigen3 Quaternions - Eigen::Quaterniond Q (0.0, Raw::fetch_ (&NH.quatern_b, is_BE), Raw::fetch_ (&NH.quatern_c, is_BE), Raw::fetch_ (&NH.quatern_d, is_BE)); - Q.w() = std::sqrt (std::max (1.0 - Q.squaredNorm(), 0.0)); - H.transform().matrix().topLeftCorner<3,3>() = Q.matrix(); - } - - H.transform().translation()[0] = Raw::fetch_ (&NH.qoffset_x, is_BE); - H.transform().translation()[1] = Raw::fetch_ (&NH.qoffset_y, is_BE); - H.transform().translation()[2] = Raw::fetch_ (&NH.qoffset_z, is_BE); - - // qfac: - float qfac = Raw::fetch_ (&NH.pixdim[0], is_BE) >= 0.0 ? 1.0 : -1.0; - if (qfac < 0.0) - H.transform().matrix().col(2) *= qfac; - } - } - else { - H.transform()(0,0) = std::numeric_limits::quiet_NaN(); - //CONF option: AnalyseLeftToRight - //CONF default: 0 (false) - //CONF A boolean value to indicate whether images in Analyse format - //CONF should be assumed to be in LAS orientation (default) or RAS - //CONF (when this is option is turned on). - if (!File::Config::get_bool ("AnalyseLeftToRight", false)) - H.stride(0) = -H.stride (0); - if (!right_left_warning_issued) { - INFO ("assuming Analyse images are encoded " + std::string (H.stride (0) > 0 ? "left to right" : "right to left")); - right_left_warning_issued = true; - } - } - - return data_offset; - } - - - - size_t read (Header& H, const nifti_2_header& NH) - { - bool is_BE = false; - if (Raw::fetch_ (&NH.sizeof_hdr, is_BE) != ver2_hdr_size) { - is_BE = true; - if (Raw::fetch_ (&NH.sizeof_hdr, is_BE) != ver2_hdr_size) - throw Exception ("image \"" + H.name() + "\" is not in NIfTI-2 format (sizeof_hdr != " + str(ver2_hdr_size) + ")"); - } - - if (memcmp (NH.magic, "n+2\0", 4) && memcmp (NH.magic, "ni2\0", 4)) - throw Exception ("image \"" + H.name() + "\" is not in NIfTI-2 format (invalid magic signature)"); - - if (memcmp (NH.magic+4, ver2_sig_extra, 4)) - WARN ("possible file transfer corruption of file \"" + H.name() + "\" (invalid magic signature)"); - - // data type: - DataType dtype; - switch (Raw::fetch_ (&NH.datatype, is_BE)) { - case DT_BINARY: - dtype = DataType::Bit; - break; - case DT_INT8: - dtype = DataType::Int8; - break; - case DT_UINT8: - dtype = DataType::UInt8; - break; - case DT_INT16: - dtype = DataType::Int16; - break; - case DT_UINT16: - dtype = DataType::UInt16; - break; - case DT_INT32: - dtype = DataType::Int32; - break; - case DT_UINT32: - dtype = DataType::UInt32; - break; - case DT_INT64: - dtype = DataType::Int64; - break; - case DT_UINT64: - dtype = DataType::UInt64; - break; - case DT_FLOAT32: - dtype = DataType::Float32; - break; - case DT_FLOAT64: - dtype = DataType::Float64; - break; - case DT_COMPLEX64: - dtype = DataType::CFloat32; - break; - case DT_COMPLEX128: - dtype = DataType::CFloat64; - break; - default: - throw Exception ("unknown data type for NIfTI-2 image \"" + H.name() + "\""); - } - - if (! (dtype.is (DataType::Bit) || dtype.is (DataType::UInt8) || dtype.is (DataType::Int8))) { - if (is_BE) - dtype.set_flag (DataType::BigEndian); - else - dtype.set_flag (DataType::LittleEndian); - } - - H.datatype() = dtype; - - if (Raw::fetch_ (&NH.bitpix, is_BE) != (int16_t) dtype.bits()) - WARN ("bitpix field does not match data type in NIfTI-2 image \"" + H.name() + "\" - ignored"); - - // data set dimensions: - const int64_t ndim = Raw::fetch_ (&NH.dim, is_BE); - if (ndim < 1) - throw Exception ("too few dimensions specified in NIfTI-2 image \"" + H.name() + "\""); - if (ndim > 7) - throw Exception ("too many dimensions specified in NIfTI-2 image \"" + H.name() + "\""); - H.ndim() = ndim; - for (int64_t i = 0; i != ndim; i++) { - H.size(i) = Raw::fetch_ (&NH.dim[i+1], is_BE); - if (H.size (i) < 0) { - INFO ("dimension along axis " + str (i) + " specified as negative in NIfTI-2 image \"" + H.name() + "\" - taking absolute value"); - H.size(i) = std::abs (H.size (i)); - } - if (!H.size (i)) - H.size(i) = 1; - H.stride(i) = i+1; - } - - // voxel sizes: - for (int i = 0; i < ndim; i++) { - H.spacing(i) = Raw::fetch_ (&NH.pixdim[i+1], is_BE); - if (H.spacing (i) < 0.0) { - INFO ("voxel size along axis " + str (i) + " specified as negative in NIfTI-2 image \"" + H.name() + "\" - taking absolute value"); - H.spacing(i) = std::abs (H.spacing (i)); - } - } - - const int64_t data_offset = Raw::fetch_ (&NH.vox_offset, is_BE); - - // offset and scale: - H.intensity_scale() = Raw::fetch_ (&NH.scl_slope, is_BE); - if (std::isfinite (H.intensity_scale()) && H.intensity_scale() != 0.0) { - H.intensity_offset() = Raw::fetch_ (&NH.scl_inter, is_BE); - if (!std::isfinite (H.intensity_offset())) - H.intensity_offset() = 0.0; - } else { - H.reset_intensity_scaling(); - } - - char descrip[81]; - strncpy (descrip, NH.descrip, 80); - if (descrip[0]) { - descrip[80] = '\0'; - if (strncmp (descrip, "MRtrix version: ", 16) == 0) - H.keyval()["mrtrix_version"] = descrip+16; - else - add_line (H.keyval()["comments"], descrip); - } - - // Note: Unlike reading from a nifti_1_header class, here we - // don't have to worry about whether or not the file is in - // Analyse format; we can treat it as a NIfTI regardless of - // whether the hedaer & data are in the same file or not. - if (Raw::fetch_ (&NH.sform_code, is_BE)) { - auto& M (H.transform().matrix()); - - M(0,0) = Raw::fetch_ (&NH.srow_x[0], is_BE); - M(0,1) = Raw::fetch_ (&NH.srow_x[1], is_BE); - M(0,2) = Raw::fetch_ (&NH.srow_x[2], is_BE); - M(0,3) = Raw::fetch_ (&NH.srow_x[3], is_BE); - - M(1,0) = Raw::fetch_ (&NH.srow_y[0], is_BE); - M(1,1) = Raw::fetch_ (&NH.srow_y[1], is_BE); - M(1,2) = Raw::fetch_ (&NH.srow_y[2], is_BE); - M(1,3) = Raw::fetch_ (&NH.srow_y[3], is_BE); - - M(2,0) = Raw::fetch_ (&NH.srow_z[0], is_BE); - M(2,1) = Raw::fetch_ (&NH.srow_z[1], is_BE); - M(2,2) = Raw::fetch_ (&NH.srow_z[2], is_BE); - M(2,3) = Raw::fetch_ (&NH.srow_z[3], is_BE); - - // get voxel sizes: - H.spacing(0) = std::sqrt (Math::pow2 (M(0,0)) + Math::pow2 (M(1,0)) + Math::pow2 (M(2,0))); - H.spacing(1) = std::sqrt (Math::pow2 (M(0,1)) + Math::pow2 (M(1,1)) + Math::pow2 (M(2,1))); - H.spacing(2) = std::sqrt (Math::pow2 (M(0,2)) + Math::pow2 (M(1,2)) + Math::pow2 (M(2,2))); - - // normalize each transform axis: - M (0,0) /= H.spacing (0); - M (1,0) /= H.spacing (0); - M (2,0) /= H.spacing (0); - - M (0,1) /= H.spacing (1); - M (1,1) /= H.spacing (1); - M (2,1) /= H.spacing (1); - - M (0,2) /= H.spacing (2); - M (1,2) /= H.spacing (2); - M (2,2) /= H.spacing (2); - - } else if (Raw::fetch_ (&NH.qform_code, is_BE)) { - { // TODO update with Eigen3 Quaternions - Eigen::Quaterniond Q (0.0, Raw::fetch_ (&NH.quatern_b, is_BE), Raw::fetch_ (&NH.quatern_c, is_BE), Raw::fetch_ (&NH.quatern_d, is_BE)); - Q.w() = std::sqrt (std::max (1.0 - Q.squaredNorm(), 0.0)); - H.transform().matrix().topLeftCorner<3,3>() = Q.matrix(); - } - - H.transform().translation()[0] = Raw::fetch_ (&NH.qoffset_x, is_BE); - H.transform().translation()[1] = Raw::fetch_ (&NH.qoffset_y, is_BE); - H.transform().translation()[2] = Raw::fetch_ (&NH.qoffset_z, is_BE); - - // qfac: - const float64 qfac = Raw::fetch_ (&NH.pixdim[0], is_BE) >= 0.0 ? 1.0 : -1.0; - if (qfac < 0.0) - H.transform().matrix().col(2) *= qfac; - } - - return data_offset; - } - - - - - - - - void check (Header& H, bool has_nii_suffix) + void check (Header& H, const bool is_analyse) { for (size_t i = 0; i < H.ndim(); ++i) if (H.size (i) < 1) @@ -486,7 +80,7 @@ namespace MR // if .img, reset all strides to defaults, since it can't be assumed // that downstream software will be able to parse the NIfTI transform - if (!has_nii_suffix) { + if (is_analyse) { for (size_t i = 0; i < H.ndim(); ++i) H.stride(i) = i+1; bool analyse_left_to_right = File::Config::get_bool ("Analyse.LeftToRight", false); @@ -544,335 +138,6 @@ namespace MR - - - - - - - - - void write (nifti_1_header& NH, const Header& H, bool has_nii_suffix) - { - if (H.ndim() > 7) - throw Exception ("NIfTI-1.1 format cannot support more than 7 dimensions for image \"" + H.name() + "\""); - - bool is_BE = H.datatype().is_big_endian(); - - std::vector axes; - auto M = adjust_transform (H, axes); - - - memset (&NH, 0, sizeof (NH)); - - // magic number: - Raw::store (ver1_hdr_size, &NH.sizeof_hdr, is_BE); - - const auto hit = H.keyval().find("comments"); - auto comments = split_lines (hit == H.keyval().end() ? std::string() : hit->second); - strncpy ( (char*) &NH.db_name, comments.size() ? comments[0].c_str() : "untitled\0\0\0\0\0\0\0\0\0\0\0", 18); - Raw::store (16384, &NH.extents, is_BE); - NH.regular = 'r'; - NH.dim_info = 0; - - // data set dimensions: - Raw::store (H.ndim(), &NH.dim[0], is_BE); - { - size_t i = 0; - for (; i < 3; i++) - Raw::store (H.size (axes[i]), &NH.dim[i+1], is_BE); - for (; i < H.ndim(); i++) - Raw::store (H.size (i), &NH.dim[i+1], is_BE); - - // pad out the other dimensions with 1, fix for fslview - ++i; - for (; i < 8; i++) - Raw::store (1, &NH.dim[i], is_BE); - } - - - // data type: - int16_t dt = 0; - switch (H.datatype()()) { - case DataType::Bit: - dt = DT_BINARY; - break; - case DataType::Int8: - dt = DT_INT8; - break; - case DataType::UInt8: - dt = DT_UINT8; - break; - case DataType::Int16LE: - case DataType::Int16BE: - dt = DT_INT16; - break; - case DataType::UInt16LE: - case DataType::UInt16BE: - dt = DT_UINT16; - break; - case DataType::Int32LE: - case DataType::Int32BE: - dt = DT_INT32; - break; - case DataType::UInt32LE: - case DataType::UInt32BE: - dt = DT_UINT32; - break; - case DataType::Int64LE: - case DataType::Int64BE: - dt = DT_INT64; - break; - case DataType::UInt64LE: - case DataType::UInt64BE: - dt = DT_UINT64; - break; - case DataType::Float32LE: - case DataType::Float32BE: - dt = DT_FLOAT32; - break; - case DataType::Float64LE: - case DataType::Float64BE: - dt = DT_FLOAT64; - break; - case DataType::CFloat32LE: - case DataType::CFloat32BE: - dt = DT_COMPLEX64; - break; - case DataType::CFloat64LE: - case DataType::CFloat64BE: - dt = DT_COMPLEX128; - break; - default: - throw Exception ("unknown data type for NIfTI-1.1 image \"" + H.name() + "\""); - } - - Raw::store (dt, &NH.datatype, is_BE); - Raw::store (H.datatype().bits(), &NH.bitpix, is_BE); - - Raw::store (1.0, &NH.pixdim[0], is_BE); - // voxel sizes: - for (size_t i = 0; i < 3; ++i) - Raw::store (H.spacing (axes[i]), &NH.pixdim[i+1], is_BE); - for (size_t i = 3; i < H.ndim(); ++i) - Raw::store (H.spacing (i), &NH.pixdim[i+1], is_BE); - - Raw::store (float32(ver1_hdr_with_ext_size), &NH.vox_offset, is_BE); - - // offset and scale: - Raw::store (H.intensity_scale(), &NH.scl_slope, is_BE); - Raw::store (H.intensity_offset(), &NH.scl_inter, is_BE); - - NH.xyzt_units = SPACE_TIME_TO_XYZT (NIFTI_UNITS_MM, NIFTI_UNITS_SEC); - - memset ((char*) &NH.descrip, 0, 80); - std::string version_string = std::string("MRtrix version: ") + App::mrtrix_version; - if (App::project_version) - version_string += std::string(", project version: ") + App::project_version; - strncpy ( (char*) &NH.descrip, version_string.c_str(), 79); - - Raw::store (NIFTI_XFORM_SCANNER_ANAT, &NH.qform_code, is_BE); - Raw::store (NIFTI_XFORM_SCANNER_ANAT, &NH.sform_code, is_BE); - - // qform: - Eigen::Matrix3d R = M.matrix().topLeftCorner<3,3>(); - if (R.determinant() < 0.0) { - R.col(2) = -R.col(2); - Raw::store (-1.0, &NH.pixdim[0], is_BE); - } - Eigen::Quaterniond Q (R); - - if (Q.w() < 0.0) - Q.vec() = -Q.vec(); - - Raw::store (Q.x(), &NH.quatern_b, is_BE); - Raw::store (Q.y(), &NH.quatern_c, is_BE); - Raw::store (Q.z(), &NH.quatern_d, is_BE); - - - // sform: - - Raw::store (M(0,3), &NH.qoffset_x, is_BE); - Raw::store (M(1,3), &NH.qoffset_y, is_BE); - Raw::store (M(2,3), &NH.qoffset_z, is_BE); - - Raw::store (H.spacing (axes[0]) * M(0,0), &NH.srow_x[0], is_BE); - Raw::store (H.spacing (axes[1]) * M(0,1), &NH.srow_x[1], is_BE); - Raw::store (H.spacing (axes[2]) * M(0,2), &NH.srow_x[2], is_BE); - Raw::store (M (0,3), &NH.srow_x[3], is_BE); - - Raw::store (H.spacing (axes[0]) * M(1,0), &NH.srow_y[0], is_BE); - Raw::store (H.spacing (axes[1]) * M(1,1), &NH.srow_y[1], is_BE); - Raw::store (H.spacing (axes[2]) * M(1,2), &NH.srow_y[2], is_BE); - Raw::store (M (1,3), &NH.srow_y[3], is_BE); - - Raw::store (H.spacing (axes[0]) * M(2,0), &NH.srow_z[0], is_BE); - Raw::store (H.spacing (axes[1]) * M(2,1), &NH.srow_z[1], is_BE); - Raw::store (H.spacing (axes[2]) * M(2,2), &NH.srow_z[2], is_BE); - Raw::store (M (2,3), &NH.srow_z[3], is_BE); - - strncpy ( (char*) &NH.magic, has_nii_suffix ? "n+1\0" : "ni1\0", 4); - } - - - - void write (nifti_2_header& NH, const Header& H, const bool has_nii_suffix) - { - if (H.ndim() > 7) - throw Exception ("NIfTI-2 format cannot support more than 7 dimensions for image \"" + H.name() + "\""); - - bool is_BE = H.datatype().is_big_endian(); - - std::vector axes; - auto M = adjust_transform (H, axes); - - - memset (&NH, 0, sizeof (NH)); - - // magic number: - Raw::store (ver2_hdr_size, &NH.sizeof_hdr, is_BE); - - strncpy ( (char*) &NH.magic, has_nii_suffix ? "n+2\0" : "ni2\0", 4); - strncpy ( (char*) &NH.magic+4, ver2_sig_extra, 4); - - // data type: - int16_t dt = 0; - switch (H.datatype()()) { - case DataType::Bit: - dt = DT_BINARY; - break; - case DataType::Int8: - dt = DT_INT8; - break; - case DataType::UInt8: - dt = DT_UINT8; - break; - case DataType::Int16LE: - case DataType::Int16BE: - dt = DT_INT16; - break; - case DataType::UInt16LE: - case DataType::UInt16BE: - dt = DT_UINT16; - break; - case DataType::Int32LE: - case DataType::Int32BE: - dt = DT_INT32; - break; - case DataType::UInt32LE: - case DataType::UInt32BE: - dt = DT_UINT32; - break; - case DataType::Int64LE: - case DataType::Int64BE: - dt = DT_INT64; - break; - case DataType::UInt64LE: - case DataType::UInt64BE: - dt = DT_UINT64; - break; - case DataType::Float32LE: - case DataType::Float32BE: - dt = DT_FLOAT32; - break; - case DataType::Float64LE: - case DataType::Float64BE: - dt = DT_FLOAT64; - break; - case DataType::CFloat32LE: - case DataType::CFloat32BE: - dt = DT_COMPLEX64; - break; - case DataType::CFloat64LE: - case DataType::CFloat64BE: - dt = DT_COMPLEX128; - break; - default: - throw Exception ("unknown data type for NIfTI-2 image \"" + H.name() + "\""); - } - Raw::store (dt, &NH.datatype, is_BE); - - Raw::store (H.datatype().bits(), &NH.bitpix, is_BE); - - // data set dimensions: - Raw::store (H.ndim(), &NH.dim[0], is_BE); - { - size_t i = 0; - for (; i < 3; i++) - Raw::store (H.size (axes[i]), &NH.dim[i+1], is_BE); - for (; i < H.ndim(); i++) - Raw::store (H.size (i), &NH.dim[i+1], is_BE); - - // pad out the other dimensions with 1, fix for fslview - ++i; - for (; i < 8; i++) - Raw::store (1, &NH.dim[i], is_BE); - } - - Raw::store (1.0, &NH.pixdim[0], is_BE); - // voxel sizes: - for (size_t i = 0; i < 3; ++i) - Raw::store (H.spacing (axes[i]), &NH.pixdim[i+1], is_BE); - for (size_t i = 3; i < H.ndim(); ++i) - Raw::store (H.spacing (i), &NH.pixdim[i+1], is_BE); - - Raw::store (int64_t(ver2_hdr_with_ext_size), &NH.vox_offset, is_BE); - - // offset and scale: - Raw::store (H.intensity_scale(), &NH.scl_slope, is_BE); - Raw::store (H.intensity_offset(), &NH.scl_inter, is_BE); - - std::string version_string = std::string("MRtrix version: ") + App::mrtrix_version; - if (App::project_version) - version_string += std::string(", project version: ") + App::project_version; - strncpy ( (char*) &NH.descrip, version_string.c_str(), 79); - - Raw::store (NIFTI_XFORM_SCANNER_ANAT, &NH.qform_code, is_BE); - Raw::store (NIFTI_XFORM_SCANNER_ANAT, &NH.sform_code, is_BE); - - // qform: - Eigen::Matrix3d R = M.matrix().topLeftCorner<3,3>(); - if (R.determinant() < 0.0) { - R.col(2) = -R.col(2); - Raw::store (-1.0, &NH.pixdim[0], is_BE); - } - Eigen::Quaterniond Q (R); - - if (Q.w() < 0.0) - Q.vec() = -Q.vec(); - - Raw::store (Q.x(), &NH.quatern_b, is_BE); - Raw::store (Q.y(), &NH.quatern_c, is_BE); - Raw::store (Q.z(), &NH.quatern_d, is_BE); - - Raw::store (M(0,3), &NH.qoffset_x, is_BE); - Raw::store (M(1,3), &NH.qoffset_y, is_BE); - Raw::store (M(2,3), &NH.qoffset_z, is_BE); - - // sform: - Raw::store (H.spacing (axes[0]) * M(0,0), &NH.srow_x[0], is_BE); - Raw::store (H.spacing (axes[1]) * M(0,1), &NH.srow_x[1], is_BE); - Raw::store (H.spacing (axes[2]) * M(0,2), &NH.srow_x[2], is_BE); - Raw::store (M (0,3), &NH.srow_x[3], is_BE); - - Raw::store (H.spacing (axes[0]) * M(1,0), &NH.srow_y[0], is_BE); - Raw::store (H.spacing (axes[1]) * M(1,1), &NH.srow_y[1], is_BE); - Raw::store (H.spacing (axes[2]) * M(1,2), &NH.srow_y[2], is_BE); - Raw::store (M (1,3), &NH.srow_y[3], is_BE); - - Raw::store (H.spacing (axes[0]) * M(2,0), &NH.srow_z[0], is_BE); - Raw::store (H.spacing (axes[1]) * M(2,1), &NH.srow_z[1], is_BE); - Raw::store (H.spacing (axes[2]) * M(2,2), &NH.srow_z[2], is_BE); - Raw::store (M (2,3), &NH.srow_z[3], is_BE); - - // TODO Test to see whether this is indeed endian-flipped - const char xyzt_units[4] { NIFTI_UNITS_MM, NIFTI_UNITS_MM, NIFTI_UNITS_MM, NIFTI_UNITS_SEC }; - const int32_t* const xyzt_units_as_int_ptr = reinterpret_cast(xyzt_units); - Raw::store (*xyzt_units_as_int_ptr, &NH.xyzt_units, is_BE); - } - - - } } } diff --git a/lib/file/nifti_utils.h b/lib/file/nifti_utils.h index 0843d4008c..976d0b9900 100644 --- a/lib/file/nifti_utils.h +++ b/lib/file/nifti_utils.h @@ -16,8 +16,9 @@ #ifndef __file_nifti_utils_h__ #define __file_nifti_utils_h__ -#include "file/nifti1.h" -#include "file/nifti2.h" +#include + +#include "types.h" namespace MR { @@ -28,20 +29,12 @@ namespace MR namespace NIfTI { - constexpr size_t ver1_hdr_size = 348; - constexpr size_t ver1_hdr_with_ext_size = 352; - constexpr size_t ver2_hdr_size = 540; - constexpr size_t ver2_hdr_with_ext_size = 544; - constexpr char ver2_sig_extra[4] { '\r', '\n', '\032', '\n' }; + bool right_left_warning_issued = false; transform_type adjust_transform (const Header& H, std::vector& order); - size_t read (Header& H, const nifti_1_header& NH); - size_t read (Header& H, const nifti_2_header& NH); - void check (Header& H, bool single_file); + void check (Header& H, const bool is_analyse); size_t version (Header& H); - void write (nifti_1_header& NH, const Header& H, bool single_file); - void write (nifti_2_header& NH, const Header& H, bool single_file); } } diff --git a/lib/formats/analyse.cpp b/lib/formats/analyse.cpp index 82f3ce9d5a..c2153e5923 100644 --- a/lib/formats/analyse.cpp +++ b/lib/formats/analyse.cpp @@ -17,6 +17,7 @@ #include "file/utils.h" #include "file/entry.h" #include "file/nifti_utils.h" +#include "file/nifti1_utils.h" #include "header.h" #include "formats/list.h" #include "image_io/default.h" @@ -30,20 +31,17 @@ namespace MR { if (!Path::has_suffix (H.name(), ".img")) return std::unique_ptr(); - const std::string header_path = H.name().substr (0, H.name().size()-4) + ".hdr"; File::MMap fmap (header_path); - if (fmap.size() == File::NIfTI::ver1_hdr_size || fmap.size() == File::NIfTI::ver1_hdr_with_ext_size) - File::NIfTI::read (H, * ( (const nifti_1_header*) fmap.address())); - else if (fmap.size() == File::NIfTI::ver2_hdr_size || fmap.size() == File::NIfTI::ver2_hdr_with_ext_size) - File::NIfTI::read (H, * ( (const nifti_2_header*) fmap.address())); - else - throw Exception ("Error reading NIfTI header file \"" + header_path + "\": Invalid size (" + str(fmap.size()) + ")"); - - std::unique_ptr io_handler (new ImageIO::Default (H)); - io_handler->files.push_back (File::Entry (H.name())); - return io_handler; + try { + File::NIfTI1::read (H, * ( (const nifti_1_header*) fmap.address())); + std::unique_ptr io_handler (new ImageIO::Default (H)); + io_handler->files.push_back (File::Entry (H.name())); + return io_handler; + } catch (...) { + return std::unique_ptr(); + } } @@ -52,17 +50,14 @@ namespace MR bool Analyse::check (Header& H, size_t num_axes) const { - if (!Path::has_suffix (H.name(), ".img")) - return false; - - if (num_axes < 3) - throw Exception ("cannot create NIfTI image with less than 3 dimensions"); + if (!Path::has_suffix (H.name(), ".img")) return false; + if (File::NIfTI::version (H) != 1) return false; - if (num_axes > 7) - throw Exception ("cannot create NIfTI image with more than 7 dimensions"); + if (num_axes < 3) throw Exception ("cannot create Analyse / NIfTI image with less than 3 dimensions"); + if (num_axes > 7) throw Exception ("cannot create Analyse / NIfTI image with more than 7 dimensions"); H.ndim() = num_axes; - File::NIfTI::check (H, false); + File::NIfTI::check (H, true); return true; } @@ -74,33 +69,16 @@ namespace MR std::unique_ptr Analyse::create (Header& H) const { if (H.ndim() > 7) - throw Exception ("NIfTI format cannot support more than 7 dimensions for image \"" + H.name() + "\""); + throw Exception ("Analyse / NIfTI format cannot support more than 7 dimensions for image \"" + H.name() + "\""); const std::string hdr_name (H.name().substr (0, H.name().size()-4) + ".hdr"); File::OFStream out (hdr_name); - - const size_t nifti_version = File::NIfTI::version (H); - switch (nifti_version) { - case 1: { - nifti_1_header NH; - File::NIfTI::write (NH, H, false); - out.write ( (char*) &NH, sizeof (nifti_1_header)); - } - break; - case 2: { - nifti_2_header NH; - File::NIfTI::write (NH, H, false); - out.write ( (char*) &NH, sizeof (nifti_2_header)); - } - break; - default: - throw Exception ("Error determining NIfTI version for file \"" + H.name() + "\""); - } - + nifti_1_header NH; + File::NIfTI1::write (NH, H, false); + out.write ( (char*) &NH, sizeof (nifti_1_header)); out.close(); File::create (H.name(), footprint(H)); - std::unique_ptr io_handler (new ImageIO::Default (H)); io_handler->files.push_back (File::Entry (H.name())); diff --git a/lib/formats/list.cpp b/lib/formats/list.cpp index 1d52468da4..2c7fba7576 100644 --- a/lib/formats/list.cpp +++ b/lib/formats/list.cpp @@ -29,8 +29,10 @@ namespace MR MRtrix mrtrix_handler; MRtrix_GZ mrtrix_gz_handler; MRI mri_handler; - NIfTI nifti_handler; - NIfTI_GZ nifti_gz_handler; + NIfTI1 nifti1_handler; + NIfTI2 nifti2_handler; + NIfTI1_GZ nifti1_gz_handler; + NIfTI2_GZ nifti2_gz_handler; Analyse analyse_handler; XDS xds_handler; DICOM dicom_handler; @@ -47,8 +49,10 @@ namespace MR &dicom_handler, &mrtrix_handler, &mrtrix_gz_handler, - &nifti_handler, - &nifti_gz_handler, + &nifti1_handler, + &nifti2_handler, + &nifti1_gz_handler, + &nifti2_gz_handler, &analyse_handler, &mri_handler, &xds_handler, diff --git a/lib/formats/list.h b/lib/formats/list.h index 88eeb458ae..51b8da8c9f 100644 --- a/lib/formats/list.h +++ b/lib/formats/list.h @@ -93,8 +93,10 @@ namespace MR DECLARE_IMAGEFORMAT (DICOM, "DICOM"); DECLARE_IMAGEFORMAT (MRtrix, "MRtrix"); DECLARE_IMAGEFORMAT (MRtrix_GZ, "MRtrix (GZip compressed)"); - DECLARE_IMAGEFORMAT (NIfTI, "NIfTI"); - DECLARE_IMAGEFORMAT (NIfTI_GZ, "NIfTI (GZip compressed)"); + DECLARE_IMAGEFORMAT (NIfTI1, "NIfTI-1.1"); + DECLARE_IMAGEFORMAT (NIfTI2, "NIfTI-2"); + DECLARE_IMAGEFORMAT (NIfTI1_GZ, "NIfTI-1.1 (GZip compressed)"); + DECLARE_IMAGEFORMAT (NIfTI2_GZ, "NIfTI-2 (GZip compressed)"); DECLARE_IMAGEFORMAT (Analyse, "AnalyseAVW / NIfTI"); DECLARE_IMAGEFORMAT (MRI, "MRTools (legacy format)"); DECLARE_IMAGEFORMAT (XDS, "XDS"); diff --git a/lib/formats/nifti.cpp b/lib/formats/nifti.cpp deleted file mode 100644 index d3ad5d3e8a..0000000000 --- a/lib/formats/nifti.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - -#include "file/ofstream.h" -#include "file/path.h" -#include "file/utils.h" -#include "file/nifti_utils.h" -#include "header.h" -#include "image_io/default.h" -#include "formats/list.h" - -namespace MR -{ - namespace Formats - { - - std::unique_ptr NIfTI::read (Header& H) const - { - if (!Path::has_suffix (H.name(), ".nii")) - return std::unique_ptr(); - - File::MMap fmap (H.name()); - size_t data_offset = 0; - try { - data_offset = File::NIfTI::read (H, * ( (const nifti_1_header*) fmap.address())); - } catch (...) { - try { - data_offset = File::NIfTI::read (H, * ( (const nifti_2_header*) fmap.address())); - } catch (...) { - throw Exception ("Error opening NIfti file \"" + H.name() + "\": Unsupported version"); - } - } - - std::unique_ptr handler (new ImageIO::Default (H)); - handler->files.push_back (File::Entry (H.name(), data_offset)); - - return std::move (handler); - } - - - - - - bool NIfTI::check (Header& H, size_t num_axes) const - { - if (!Path::has_suffix (H.name(), ".nii")) return (false); - if (num_axes < 3) throw Exception ("cannot create NIfTI image with less than 3 dimensions"); - if (num_axes > 7) throw Exception ("cannot create NIfTI image with more than 7 dimensions"); - - H.ndim() = num_axes; - File::NIfTI::check (H, true); - - return true; - } - - - - - - std::unique_ptr NIfTI::create (Header& H) const - { - if (H.ndim() > 7) - throw Exception ("NIfTI format cannot support more than 7 dimensions for image \"" + H.name() + "\""); - - const size_t nifti_version = File::NIfTI::version (H); - size_t data_offset = 0; - File::OFStream out (H.name(), std::ios::out | std::ios::binary); - switch (nifti_version) { - case 1: { - nifti_1_header NH; - File::NIfTI::write (NH, H, true); - out.write ( (char*) &NH, sizeof (nifti_1_header)); - data_offset = File::NIfTI::ver1_hdr_with_ext_size; - DEBUG ("Image \"" + H.name() + "\" being created with NIfTI version 1.1"); - } - break; - case 2: { - nifti_2_header NH; - File::NIfTI::write (NH, H, true); - out.write ( (char*) &NH, sizeof (nifti_2_header)); - data_offset = File::NIfTI::ver2_hdr_with_ext_size; - DEBUG ("Image \"" + H.name() + "\" being created with NIfTI version 2"); - } - break; - default: - throw Exception ("Error determining NIfTI version for file \"" + H.name() + "\""); - } - - nifti1_extender extender; - memset (extender.extension, 0x00, sizeof (nifti1_extender)); - out.write (extender.extension, sizeof (nifti1_extender)); - out.close(); - - File::resize (H.name(), data_offset + footprint(H)); - - std::unique_ptr handler (new ImageIO::Default (H)); - handler->files.push_back (File::Entry (H.name(), data_offset)); - - return std::move (handler); - } - - - - } -} - diff --git a/lib/formats/nifti1.cpp b/lib/formats/nifti1.cpp new file mode 100644 index 0000000000..afceaa1fdf --- /dev/null +++ b/lib/formats/nifti1.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "file/ofstream.h" +#include "file/path.h" +#include "file/utils.h" +#include "file/nifti_utils.h" +#include "file/nifti1_utils.h" +#include "header.h" +#include "image_io/default.h" +#include "formats/list.h" + +namespace MR +{ + namespace Formats + { + + std::unique_ptr NIfTI1::read (Header& H) const + { + if (!Path::has_suffix (H.name(), ".nii")) + return std::unique_ptr(); + + try { + File::MMap fmap (H.name()); + const size_t data_offset = File::NIfTI1::read (H, * ( (const nifti_1_header*) fmap.address())); + std::unique_ptr handler (new ImageIO::Default (H)); + handler->files.push_back (File::Entry (H.name(), data_offset)); + return std::move (handler); + } catch (...) { + return std::unique_ptr(); + } + } + + + + + + bool NIfTI1::check (Header& H, size_t num_axes) const + { + if (!Path::has_suffix (H.name(), ".nii")) return (false); + if (File::NIfTI::version (H) != 1) return false; + + if (num_axes < 3) throw Exception ("cannot create NIfTI-1.1 image with less than 3 dimensions"); + if (num_axes > 7) throw Exception ("cannot create NIfTI-1.1 image with more than 7 dimensions"); + + H.ndim() = num_axes; + File::NIfTI::check (H, false); + + return true; + } + + + + + + std::unique_ptr NIfTI1::create (Header& H) const + { + if (H.ndim() > 7) + throw Exception ("NIfTI-1.1 format cannot support more than 7 dimensions for image \"" + H.name() + "\""); + + nifti_1_header NH; + File::NIfTI1::write (NH, H, true); + File::OFStream out (H.name(), std::ios::out | std::ios::binary); + out.write ( (char*) &NH, sizeof (nifti_1_header)); + nifti1_extender extender; + memset (extender.extension, 0x00, sizeof (nifti1_extender)); + out.write (extender.extension, sizeof (nifti1_extender)); + out.close(); + + File::resize (H.name(), File::NIfTI1::header_with_ext_size + footprint(H)); + + std::unique_ptr handler (new ImageIO::Default (H)); + handler->files.push_back (File::Entry (H.name(), File::NIfTI1::header_with_ext_size)); + + return std::move (handler); + } + + + + } +} + diff --git a/lib/formats/nifti1_gz.cpp b/lib/formats/nifti1_gz.cpp new file mode 100644 index 0000000000..bb5c8d00cb --- /dev/null +++ b/lib/formats/nifti1_gz.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "file/utils.h" +#include "file/path.h" +#include "file/gz.h" +#include "file/nifti_utils.h" +#include "file/nifti1_utils.h" +#include "header.h" +#include "image_io/gz.h" +#include "formats/list.h" + +namespace MR +{ + namespace Formats + { + + + std::unique_ptr NIfTI1_GZ::read (Header& H) const + { + if (!Path::has_suffix (H.name(), ".nii.gz")) + return std::unique_ptr(); + + nifti_1_header NH; + File::GZ zf (H.name(), "rb"); + zf.read (reinterpret_cast (&NH), File::NIfTI1::header_size); + zf.close(); + + try { + const size_t data_offset = File::NIfTI1::read (H, NH); + std::unique_ptr io_handler (new ImageIO::GZ (H, data_offset)); + memcpy (io_handler.get()->header(), &NH, File::NIfTI1::header_size); + memset (io_handler.get()->header() + File::NIfTI1::header_size, 0, sizeof(nifti1_extender)); + io_handler->files.push_back (File::Entry (H.name(), data_offset)); + return std::move (io_handler); + } catch (...) { + return std::unique_ptr(); + } + } + + + + + + bool NIfTI1_GZ::check (Header& H, size_t num_axes) const + { + if (!Path::has_suffix (H.name(), ".nii.gz")) return false; + if (File::NIfTI::version (H) != 1) return false; + + if (num_axes < 3) throw Exception ("cannot create NIfTI-1.1 image with less than 3 dimensions"); + if (num_axes > 7) throw Exception ("cannot create NIfTI-1.1 image with more than 7 dimensions"); + + H.ndim() = num_axes; + File::NIfTI::check (H, true); + + return true; + } + + + + + + std::unique_ptr NIfTI1_GZ::create (Header& H) const + { + if (H.ndim() > 7) + throw Exception ("NIfTI-1.1 format cannot support more than 7 dimensions for image \"" + H.name() + "\""); + + std::unique_ptr io_handler (new ImageIO::GZ (H, File::NIfTI1::header_with_ext_size)); + + File::NIfTI1::write (*reinterpret_cast (io_handler->header()), H, true); + memset (io_handler->header()+File::NIfTI1::header_size, 0, sizeof(nifti1_extender)); + + File::create (H.name()); + io_handler->files.push_back (File::Entry (H.name(), File::NIfTI1::header_with_ext_size)); + + return std::move (io_handler); + } + + + + } +} + diff --git a/lib/formats/nifti2.cpp b/lib/formats/nifti2.cpp new file mode 100644 index 0000000000..9f54abafe3 --- /dev/null +++ b/lib/formats/nifti2.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "file/ofstream.h" +#include "file/path.h" +#include "file/utils.h" +#include "file/nifti1.h" +#include "file/nifti_utils.h" +#include "file/nifti2_utils.h" +#include "header.h" +#include "image_io/default.h" +#include "formats/list.h" + +namespace MR +{ + namespace Formats + { + + std::unique_ptr NIfTI2::read (Header& H) const + { + if (!Path::has_suffix (H.name(), ".nii") && !Path::has_suffix (H.name(), ".img")) + return std::unique_ptr(); + + const std::string header_path = Path::has_suffix (H.name(), ".nii") ? + H.name() : + H.name().substr (0, H.name().size()-4) + ".hdr"; + + File::MMap fmap (header_path); + + try { + const size_t data_offset = File::NIfTI2::read (H, * ( (const nifti_2_header*) fmap.address())); + std::unique_ptr handler (new ImageIO::Default (H)); + handler->files.push_back (File::Entry (H.name(), data_offset)); + return std::move (handler); + } catch (...) { + return std::unique_ptr(); + } + } + + + + + + bool NIfTI2::check (Header& H, size_t num_axes) const + { + if (!Path::has_suffix (H.name(), ".nii") && !Path::has_suffix (H.name(), ".img")) return (false); + if (File::NIfTI::version (H) != 2) return false; + + if (num_axes < 3) throw Exception ("cannot create NIfTI-2 image with less than 3 dimensions"); + if (num_axes > 7) throw Exception ("cannot create NIfTI-2 image with more than 7 dimensions"); + + H.ndim() = num_axes; + // Even though the file may be split across .img/.hdr files, there's no risk + // of it being interpreted as an Analyse image because it's NIfTI-2 + File::NIfTI::check (H, false); + + return true; + } + + + + + + std::unique_ptr NIfTI2::create (Header& H) const + { + if (H.ndim() > 7) + throw Exception ("NIfTI-2 format cannot support more than 7 dimensions for image \"" + H.name() + "\""); + + const bool single_file = Path::has_suffix (H.name(), ".nii"); + const std::string header_path = single_file ? H.name() : H.name().substr (0, H.name().size()-4) + ".hdr"; + + nifti_2_header NH; + File::NIfTI2::write (NH, H, true); + File::OFStream out (header_path, std::ios::out | std::ios::binary); + out.write ( (char*) &NH, sizeof (nifti_2_header)); + nifti1_extender extender; + memset (extender.extension, 0x00, sizeof (nifti1_extender)); + out.write (extender.extension, sizeof (nifti1_extender)); + out.close(); + + const size_t data_offset = single_file ? File::NIfTI2::header_with_ext_size : 0; + + if (single_file) + File::resize (H.name(), data_offset + footprint(H)); + else + File::create (H.name(), footprint(H)); + + std::unique_ptr handler (new ImageIO::Default (H)); + handler->files.push_back (File::Entry (H.name(), data_offset)); + + return std::move (handler); + } + + + + } +} + diff --git a/lib/formats/nifti2_gz.cpp b/lib/formats/nifti2_gz.cpp new file mode 100644 index 0000000000..becc0caa27 --- /dev/null +++ b/lib/formats/nifti2_gz.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "file/utils.h" +#include "file/path.h" +#include "file/gz.h" +#include "file/nifti1.h" +#include "file/nifti_utils.h" +#include "file/nifti2_utils.h" +#include "header.h" +#include "image_io/gz.h" +#include "formats/list.h" + +namespace MR +{ + namespace Formats + { + + + std::unique_ptr NIfTI2_GZ::read (Header& H) const + { + if (!Path::has_suffix (H.name(), ".nii.gz")) + return std::unique_ptr(); + + nifti_2_header NH; + File::GZ zf (H.name(), "rb"); + zf.read (reinterpret_cast (&NH), File::NIfTI2::header_size); + zf.close(); + const size_t data_offset = File::NIfTI2::read (H, NH); + + std::unique_ptr io_handler (new ImageIO::GZ (H, data_offset)); + memcpy (io_handler.get()->header(), &NH, File::NIfTI2::header_size); + memset (io_handler.get()->header() + File::NIfTI2::header_size, 0, sizeof(nifti1_extender)); + io_handler->files.push_back (File::Entry (H.name(), data_offset)); + + return std::move (io_handler); + } + + + + + + bool NIfTI2_GZ::check (Header& H, size_t num_axes) const + { + if (!Path::has_suffix (H.name(), ".nii.gz")) return false; + if (File::NIfTI::version (H) != 2) return false; + + if (num_axes < 3) throw Exception ("cannot create NIfTI-2 image with less than 3 dimensions"); + if (num_axes > 7) throw Exception ("cannot create NIfTI-2 image with more than 7 dimensions"); + + H.ndim() = num_axes; + File::NIfTI::check (H, true); + + return true; + } + + + + + + std::unique_ptr NIfTI2_GZ::create (Header& H) const + { + if (H.ndim() > 7) + throw Exception ("NIfTI-2 format cannot support more than 7 dimensions for image \"" + H.name() + "\""); + + std::unique_ptr io_handler (new ImageIO::GZ (H, File::NIfTI2::header_with_ext_size)); + + File::NIfTI2::write (*reinterpret_cast (io_handler->header()), H, true); + memset (io_handler->header()+File::NIfTI2::header_size, 0, sizeof(nifti1_extender)); + + File::create (H.name()); + io_handler->files.push_back (File::Entry (H.name(), File::NIfTI2::header_with_ext_size)); + + return std::move (io_handler); + } + + + + } +} + diff --git a/lib/formats/nifti_gz.cpp b/lib/formats/nifti_gz.cpp deleted file mode 100644 index b5e540f432..0000000000 --- a/lib/formats/nifti_gz.cpp +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - -#include "file/utils.h" -#include "file/path.h" -#include "file/gz.h" -#include "file/nifti_utils.h" -#include "header.h" -#include "image_io/gz.h" -#include "formats/list.h" - -namespace MR -{ - namespace Formats - { - - - std::unique_ptr NIfTI_GZ::read (Header& H) const - { - if (!Path::has_suffix (H.name(), ".nii.gz")) - return std::unique_ptr(); - - nifti_1_header N1H; - nifti_2_header N2H; - size_t data_offset = 0; - - try { - File::GZ zf (H.name(), "rb"); - zf.read (reinterpret_cast (&N1H), File::NIfTI::ver1_hdr_size); - zf.close(); - data_offset = File::NIfTI::read (H, N1H); - } catch (...) { - try { - File::GZ zf (H.name(), "rb"); - zf.read (reinterpret_cast (&N2H), File::NIfTI::ver2_hdr_size); - zf.close(); - data_offset = File::NIfTI::read (H, N2H); - } catch (...) { - throw Exception ("Error opening NIfti file \"" + H.name() + "\": Unsupported version"); - } - } - - std::unique_ptr io_handler (new ImageIO::GZ (H, data_offset)); - if (data_offset == File::NIfTI::ver1_hdr_with_ext_size) { - memcpy (io_handler.get()->header(), &N1H, File::NIfTI::ver1_hdr_size); - memset (io_handler.get()->header() + File::NIfTI::ver1_hdr_size, 0, sizeof(nifti1_extender)); - } else { - memcpy (io_handler.get()->header(), &N2H, File::NIfTI::ver2_hdr_size); - memset (io_handler.get()->header() + File::NIfTI::ver2_hdr_size, 0, sizeof(nifti1_extender)); - } - io_handler->files.push_back (File::Entry (H.name(), data_offset)); - - return std::move (io_handler); - } - - - - - - bool NIfTI_GZ::check (Header& H, size_t num_axes) const - { - if (!Path::has_suffix (H.name(), ".nii.gz")) - return false; - - if (num_axes < 3) - throw Exception ("cannot create NIfTI image with less than 3 dimensions"); - - if (num_axes > 7) - throw Exception ("cannot create NIfTI image with more than 7 dimensions"); - - H.ndim() = num_axes; - File::NIfTI::check (H, true); - - return true; - } - - - - - - std::unique_ptr NIfTI_GZ::create (Header& H) const - { - if (H.ndim() > 7) - throw Exception ("NIfTI format cannot support more than 7 dimensions for image \"" + H.name() + "\""); - - const size_t nifti_version = File::NIfTI::version (H); - size_t data_offset = 0; - switch (nifti_version) { - case 1: data_offset = File::NIfTI::ver1_hdr_with_ext_size; break; - case 2: data_offset = File::NIfTI::ver2_hdr_with_ext_size; break; - default: throw Exception ("Error determining NIfTI version for file \"" + H.name() + "\""); - } - - std::unique_ptr io_handler (new ImageIO::GZ (H, data_offset)); - - switch (nifti_version) { - case 1: - File::NIfTI::write (*reinterpret_cast (io_handler->header()), H, true); - memset (io_handler->header()+File::NIfTI::ver1_hdr_size, 0, sizeof(nifti1_extender)); - break; - case 2: - File::NIfTI::write (*reinterpret_cast (io_handler->header()), H, true); - memset (io_handler->header()+File::NIfTI::ver2_hdr_size, 0, sizeof(nifti1_extender)); - break; - default: - break; - } - - File::create (H.name()); - io_handler->files.push_back (File::Entry (H.name(), data_offset)); - - return std::move (io_handler); - } - - - - } -} - From d5a5299929bd4fa3882a2e038e8f25dcbcd41cc9 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 1 Aug 2016 16:30:43 +1000 Subject: [PATCH 044/723] mrclusterstats: Create images as required Rather than creating all images before processing occurs, create and free them as the relevant output occurs. This should prevent formation of erroneous files if permutation testing is not performed, prevent unnecessary memory usage if those files cannot be memory-mapped, and give more indicative progress bars as outputs are generated. --- cmd/mrclusterstats.cpp | 86 ++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 36 deletions(-) diff --git a/cmd/mrclusterstats.cpp b/cmd/mrclusterstats.cpp index 4d12ebb054..6e26464249 100644 --- a/cmd/mrclusterstats.cpp +++ b/cmd/mrclusterstats.cpp @@ -184,27 +184,7 @@ void run() { } const std::string prefix (argument[4]); - - auto cluster_image = Image::create (prefix + (use_tfce ? "tfce.mif" : "cluster_sizes.mif"), output_header); - auto tvalue_image = Image::create (prefix + "tvalue.mif", output_header); - auto fwe_pvalue_image = Image::create (prefix + "fwe_pvalue.mif", output_header); - auto uncorrected_pvalue_image = Image::create (prefix + "uncorrected_pvalue.mif", output_header); - auto abs_effect_image = Image::create (prefix + "abs_effect.mif", output_header); - auto std_effect_image = Image::create (prefix + "std_effect.mif", output_header); - auto std_dev_image = Image::create (prefix + "std_dev.mif", output_header); - std::vector> beta_images; - for (ssize_t i = 0; i < contrast.cols(); ++i) - beta_images.push_back(Image::create (prefix + "beta" + str(i) + ".mif", output_header)); - Image cluster_image_neg; - Image fwe_pvalue_image_neg; - Image uncorrected_pvalue_image_neg; - - bool compute_negative_contrast = get_options("negative").size() ? true : false; - if (compute_negative_contrast) { - cluster_image_neg = Image::create (prefix + (use_tfce ? "tfce_neg.mif" : "cluster_sizes_neg.mif"), output_header); - fwe_pvalue_image_neg = Image::create (prefix + "fwe_pvalue_neg.mif", output_header); - uncorrected_pvalue_image_neg = Image::create (prefix + "uncorrected_pvalue_neg.mif", output_header); - } + bool compute_negative_contrast = get_options("negative").size(); vector_type perm_distribution (num_perms); std::shared_ptr perm_distribution_neg; @@ -236,21 +216,44 @@ void run() { { ProgressBar progress ("generating pre-permutation output", (compute_negative_contrast ? 3 : 2) + contrast.cols() + 3); - write_output (tvalue_output, mask_indices, tvalue_image); ++progress; - write_output (default_cluster_output, mask_indices, cluster_image); ++progress; + { + auto tvalue_image = Image::create (prefix + "tvalue.mif", output_header); + write_output (tvalue_output, mask_indices, tvalue_image); + } + ++progress; + { + auto cluster_image = Image::create (prefix + (use_tfce ? "tfce.mif" : "cluster_sizes.mif"), output_header); + write_output (default_cluster_output, mask_indices, cluster_image); + } + ++progress; if (compute_negative_contrast) { - write_output (*default_cluster_output_neg, mask_indices, cluster_image_neg); ++progress; + auto cluster_image_neg = Image::create (prefix + (use_tfce ? "tfce_neg.mif" : "cluster_sizes_neg.mif"), output_header); + write_output (*default_cluster_output_neg, mask_indices, cluster_image_neg); + ++progress; } auto temp = Math::Stats::GLM::solve_betas (data, design); for (ssize_t i = 0; i < contrast.cols(); ++i) { - write_output (temp.row(i), mask_indices, beta_images[i]); ++progress; + auto beta_image = Image::create (prefix + "beta" + str(i) + ".mif", output_header); + write_output (temp.row(i), mask_indices, beta_image); + ++progress; + } + { + const auto temp = Math::Stats::GLM::abs_effect_size (data, design, contrast); + auto abs_effect_image = Image::create (prefix + "abs_effect.mif", output_header); + write_output (temp.row(0), mask_indices, abs_effect_image); + } + ++progress; + { + const auto temp = Math::Stats::GLM::std_effect_size (data, design, contrast); + auto std_effect_image = Image::create (prefix + "std_effect.mif", output_header); + write_output (temp.row(0), mask_indices, std_effect_image); + } + ++progress; + { + const auto temp = Math::Stats::GLM::stdev (data, design); + auto std_dev_image = Image::create (prefix + "std_dev.mif", output_header); + write_output (temp.row(0), mask_indices, std_dev_image); } - temp = Math::Stats::GLM::abs_effect_size (data, design, contrast); - write_output (temp.row(0), mask_indices, abs_effect_image); ++progress; - temp = Math::Stats::GLM::std_effect_size (data, design, contrast); - write_output (temp.row(0), mask_indices, std_effect_image); ++progress; - temp = Math::Stats::GLM::stdev (data, design); - write_output (temp.row(0), mask_indices, std_dev_image); } auto opt = get_options ("notest"); @@ -266,14 +269,25 @@ void run() { { ProgressBar progress ("generating output", compute_negative_contrast ? 4 : 2); - write_output (uncorrected_pvalue, mask_indices, uncorrected_pvalue_image); ++progress; - vector_type fwe_pvalue_output (num_vox); - Math::Stats::Permutation::statistic2pvalue (perm_distribution, default_cluster_output, fwe_pvalue_output); - write_output (fwe_pvalue_output, mask_indices, fwe_pvalue_image); ++progress; + { + auto uncorrected_pvalue_image = Image::create (prefix + "uncorrected_pvalue.mif", output_header); + write_output (uncorrected_pvalue, mask_indices, uncorrected_pvalue_image); + } + ++progress; + { + vector_type fwe_pvalue_output (num_vox); + Math::Stats::Permutation::statistic2pvalue (perm_distribution, default_cluster_output, fwe_pvalue_output); + auto fwe_pvalue_image = Image::create (prefix + "fwe_pvalue.mif", output_header); + write_output (fwe_pvalue_output, mask_indices, fwe_pvalue_image); + } + ++progress; if (compute_negative_contrast) { - write_output (*uncorrected_pvalue_neg, mask_indices, uncorrected_pvalue_image_neg); ++progress; + auto uncorrected_pvalue_image_neg = Image::create (prefix + "uncorrected_pvalue_neg.mif", output_header); + write_output (*uncorrected_pvalue_neg, mask_indices, uncorrected_pvalue_image_neg); + ++progress; vector_type fwe_pvalue_output_neg (num_vox); Math::Stats::Permutation::statistic2pvalue (*perm_distribution_neg, *default_cluster_output_neg, fwe_pvalue_output_neg); + auto fwe_pvalue_image_neg = Image::create (prefix + "fwe_pvalue_neg.mif", output_header); write_output (fwe_pvalue_output_neg, mask_indices, fwe_pvalue_image_neg); } } From e3d141a2dc6ebb8861b312269d38bde1255f65d7 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 1 Aug 2016 16:39:20 +1000 Subject: [PATCH 045/723] Docs update following merges into 'standford' branch --- docs/reference/config_file_options.rst | 5 +++++ docs/reference/scripts/population_template.rst | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/docs/reference/config_file_options.rst b/docs/reference/config_file_options.rst index a2467f045a..bb5a953b1c 100644 --- a/docs/reference/config_file_options.rst +++ b/docs/reference/config_file_options.rst @@ -284,6 +284,11 @@ List of MRtrix3 configuration file options A boolean value to indicate whether bitwise storage of binary data is permitted (most 3rd party software packages don't support bitwise data). If false (the default), data will be stored using more widely supported unsigned 8-bit integers. +* **NIFTI.AlwaysUseVer2** + *default: 0 (false)* + + A boolean value to indicate whether NIfTI images should always be written in the new NIfTI-2 format. If false, images will be written in the older NIfTI-1 format by default, with the exception being files where the number of voxels along any axis exceeds the maximum permissible in that format (32767), in which case the output file will automatically switch to the NIfTI-2 format. + * **NeedOpenGLCoreProfile** *default: 1 (true)* diff --git a/docs/reference/scripts/population_template.rst b/docs/reference/scripts/population_template.rst index eedd209e13..20d2c7535a 100644 --- a/docs/reference/scripts/population_template.rst +++ b/docs/reference/scripts/population_template.rst @@ -30,19 +30,27 @@ Options for the population_template script - **-transformed_dir** Output a directory containing the input images transformed to the template. If the folder does not exist it will be created +- **-linear_transformations_dir** Output a directory containing the linear transformations used to generate the template. If the folder does not exist it will be created + - **-template_mask** Output a template mask. Only works in -mask_dir has been input. The template mask is computed as the intersection of all subject masks in template space. - **-rigid** perform rigid registration instead of affine. This should be used for intra-subject registration in longitudinal analysis -- **-linear_scale** Specifiy the multi-resolution pyramid used to build the rigid or affine template, in the form of a list of scale factors (default: 0.3,0.4,0.5,0.6,0.7,1.0,1.0,1.0,1.0,1.0). This implicitly defines the number of template levels +- **-linear_no_pause** Do not pause the script if a linear registration seems implausible + +- **-linear_scale** Specifiy the multi-resolution pyramid used to build the rigid or affine template, in the form of a list of scale factors (default: 0.3,0.4,0.6,0.8,1.0,1.0). This implicitly defines the number of template levels + +- **-linear_lmax** Specifiy the lmax used for rigid or affine registration for each scale factor, in the form of a list of integers (default: 2,2,2,4,4,4). The list must be the same length as the linear_scale factor list + +- **-linear_niter** Specifiy the number of registration iterations used within each level before updating the template, in the form of a list of integers (default:500 for each scale). The must be a single number or a list of same length as the linear_scale factor list -- **-linear_lmax** Specifiy the lmax used for rigid or affine registration for each scale factor, in the form of a list of integers (default: 0,0,2,2,2,2,2,2,4,4,4,4,4,4,4,4). The list must be the same length as the affine_scale factor list +- **-linear_estimator** Choose estimator for intensity difference metric. Valid choices are: l1 (least absolute: |x|), l2 (ordinary least squares), lp (least powers: |x|^1.2), Default: l2 - **-nl_scale** Specifiy the multi-resolution pyramid used to build the non-linear template, in the form of a list of scale factors (default: 0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0). This implicitly defines the number of template levels -- **-nl_lmax** Specifiy the lmax used for non-linear registration for each scale factor, in the form of a list of integers (default: 0,0,2,2,2,2,2,2,2,2,4,4,4,4). The list must be the same length as the nl_scale factor list +- **-nl_lmax** Specifiy the lmax used for non-linear registration for each scale factor, in the form of a list of integers (default: 2,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4). The list must be the same length as the nl_scale factor list -- **-nl_niter** Specifiy the number of registration iterations used within each level before updating the template, in the form of a list of integers (default:5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5). The list must be the same length as the nl_scale factor list +- **-nl_niter** Specifiy the number of registration iterations used within each level before updating the template, in the form of a list of integers (default: 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5). The list must be the same length as the nl_scale factor list - **-nl_update_smooth** Regularise the gradient update field with Gaussian smoothing (standard deviation in voxel units, Default 2.0 x voxel_size) @@ -50,7 +58,7 @@ Options for the population_template script - **-nl_grad_step** The gradient step size for non-linear registration (Default: 0.5) -- **-noreorientation** Turn off FOD reorientation. Reorientation is on by default if the number of volumes in the 4th dimension corresponds to the number of coefficients in an antipodally symmetric spherical harmonic series (i.e. 6, 15, 28, 45, 66 etc +- **-noreorientation** Turn off FOD reorientation in mrregister. Reorientation is on by default if the number of volumes in the 4th dimension corresponds to the number of coefficients in an antipodally symmetric spherical harmonic series (i.e. 6, 15, 28, 45, 66 etc - **-initial_alignment** Method of alignment to form the initial template. Options are "mass" (default), "geometric" and "none". From 986b000dd4d5b068599729985500a059fd8772f0 Mon Sep 17 00:00:00 2001 From: rtabbara Date: Tue, 2 Aug 2016 13:35:25 +1000 Subject: [PATCH 046/723] fod2fixel: Creating a lightweight version of FODlobes to cache as we iterate over image * Should reduce the memory used as we perform segmentation --- cmd/fod2fixel.cpp | 46 +++++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/cmd/fod2fixel.cpp b/cmd/fod2fixel.cpp index 14a908906e..0ebd5f8068 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -105,10 +105,6 @@ void usage () - - - - class Segmented_FOD_receiver { @@ -134,9 +130,30 @@ class Segmented_FOD_receiver private: + struct Primitive_FOD_lobe { + Eigen::Vector3f mean_dir; + float integral; + float peak_value; + Primitive_FOD_lobe (Eigen::Vector3f mean_dir, float integral, float peak_value) + : mean_dir (mean_dir), integral (integral), peak_value (peak_value) {} + }; + + + class Primitive_FOD_lobes : public std::vector { + public: + Eigen::Array3i vox; + + Primitive_FOD_lobes (const FOD_lobes& in) : vox (in.vox) + { + for (const FOD_lobe& lobe : in) { + this->emplace_back (lobe.get_mean_dir (), lobe.get_integral (), lobe.get_peak_value ()); + } + } + }; + Header H; std::string fixel_folder_path, index_path, dir_path, afd_path, peak_path, disp_path; - std::vector lobes; + std::vector lobes; uint64_t n_fixels; }; @@ -248,28 +265,28 @@ void Segmented_FOD_receiver::commit () if (dir_image) { for (size_t i = 0; i < n_vox_fixels; ++i) { dir_image->index (0) = offset + i; - dir_image->row (1) = vox_fixels[i].get_mean_dir (); + dir_image->row (1) = vox_fixels[i].mean_dir; } } if (afd_image) { for (size_t i = 0; i < n_vox_fixels; ++i) { afd_image->index (0) = offset + i; - afd_image->value () = vox_fixels[i].get_integral (); + afd_image->value () = vox_fixels[i].integral; } } if (peak_image) { for (size_t i = 0; i < n_vox_fixels; ++i) { peak_image->index (0) = offset + i; - peak_image->value () = vox_fixels[i].get_peak_value (); + peak_image->value () = vox_fixels[i].peak_value; } } if (disp_image) { for (size_t i = 0; i < n_vox_fixels; ++i) { disp_image->index (0) = offset + i; - disp_image->value () = vox_fixels[i].get_integral () / vox_fixels[i].get_peak_value (); + disp_image->value () = vox_fixels[i].integral / vox_fixels[i].peak_value; } } @@ -324,13 +341,16 @@ void run () if (!set_directions_file) WARN ("No explicit directions image filename specified. Generating default " + default_directions_filename); - FMLS::FODQueueWriter writer (fod_data, mask); + std::unique_ptr writer (new FMLS::FODQueueWriter(fod_data, mask)); const DWI::Directions::Set dirs (1281); - Segmenter fmls (dirs, Math::SH::LforN (H.size(3))); - load_fmls_thresholds (fmls); + std::unique_ptr fmls (new Segmenter (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); - Thread::run_queue (writer, Thread::batch (SH_coefs()), Thread::multi (fmls), Thread::batch (FOD_lobes()), receiver); + writer.release (); + fmls.release (); receiver.commit (); } From 2a066140e2469d833f819ee3b760ff3da5af1508 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 2 Aug 2016 12:11:26 -0700 Subject: [PATCH 047/723] mrclusterstats: Minor tweaks --- cmd/mrclusterstats.cpp | 46 ++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/cmd/mrclusterstats.cpp b/cmd/mrclusterstats.cpp index 6e26464249..4407513bf1 100644 --- a/cmd/mrclusterstats.cpp +++ b/cmd/mrclusterstats.cpp @@ -209,6 +209,7 @@ void run() { if (!use_tfce) throw Exception ("nonstationary adjustment is not currently implemented for threshold-based cluster analysis"); Stats::PermTest::precompute_empirical_stat (glm, enhancer, nperms_nonstationary, empirical_enhanced_statistic); + save_matrix (empirical_enhanced_statistic, prefix + "empirical.txt"); } Stats::PermTest::precompute_default_permutation (glm, enhancer, empirical_enhanced_statistic, @@ -256,40 +257,37 @@ void run() { } } - auto opt = get_options ("notest"); - if (!opt.size()) { + if (!get_options ("notest").size()) { Stats::PermTest::run_permutations (glm, enhancer, num_perms, empirical_enhanced_statistic, default_cluster_output, default_cluster_output_neg, perm_distribution, perm_distribution_neg, uncorrected_pvalue, uncorrected_pvalue_neg); - save_matrix (perm_distribution, prefix + "perm_dist.txt"); + save_matrix (perm_distribution, prefix + "perm_dist.txt"); if (compute_negative_contrast) save_matrix (*perm_distribution_neg, prefix + "perm_dist_neg.txt"); + ProgressBar progress ("generating output", compute_negative_contrast ? 4 : 2); { - ProgressBar progress ("generating output", compute_negative_contrast ? 4 : 2); - { - auto uncorrected_pvalue_image = Image::create (prefix + "uncorrected_pvalue.mif", output_header); - write_output (uncorrected_pvalue, mask_indices, uncorrected_pvalue_image); - } - ++progress; - { - vector_type fwe_pvalue_output (num_vox); - Math::Stats::Permutation::statistic2pvalue (perm_distribution, default_cluster_output, fwe_pvalue_output); - auto fwe_pvalue_image = Image::create (prefix + "fwe_pvalue.mif", output_header); - write_output (fwe_pvalue_output, mask_indices, fwe_pvalue_image); - } + auto uncorrected_pvalue_image = Image::create (prefix + "uncorrected_pvalue.mif", output_header); + write_output (uncorrected_pvalue, mask_indices, uncorrected_pvalue_image); + } + ++progress; + { + vector_type fwe_pvalue_output (num_vox); + Math::Stats::Permutation::statistic2pvalue (perm_distribution, default_cluster_output, fwe_pvalue_output); + auto fwe_pvalue_image = Image::create (prefix + "fwe_pvalue.mif", output_header); + write_output (fwe_pvalue_output, mask_indices, fwe_pvalue_image); + } + ++progress; + if (compute_negative_contrast) { + auto uncorrected_pvalue_image_neg = Image::create (prefix + "uncorrected_pvalue_neg.mif", output_header); + write_output (*uncorrected_pvalue_neg, mask_indices, uncorrected_pvalue_image_neg); ++progress; - if (compute_negative_contrast) { - auto uncorrected_pvalue_image_neg = Image::create (prefix + "uncorrected_pvalue_neg.mif", output_header); - write_output (*uncorrected_pvalue_neg, mask_indices, uncorrected_pvalue_image_neg); - ++progress; - vector_type fwe_pvalue_output_neg (num_vox); - Math::Stats::Permutation::statistic2pvalue (*perm_distribution_neg, *default_cluster_output_neg, fwe_pvalue_output_neg); - auto fwe_pvalue_image_neg = Image::create (prefix + "fwe_pvalue_neg.mif", output_header); - write_output (fwe_pvalue_output_neg, mask_indices, fwe_pvalue_image_neg); - } + vector_type fwe_pvalue_output_neg (num_vox); + Math::Stats::Permutation::statistic2pvalue (*perm_distribution_neg, *default_cluster_output_neg, fwe_pvalue_output_neg); + auto fwe_pvalue_image_neg = Image::create (prefix + "fwe_pvalue_neg.mif", output_header); + write_output (fwe_pvalue_output_neg, mask_indices, fwe_pvalue_image_neg); } } From b0638896733eb94dd15aac832a2efa0ad5eacbd8 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 2 Aug 2016 12:15:02 -0700 Subject: [PATCH 048/723] JSON file support New options in mrconvert and mrinfo to import and export JSON files respectively. JSON functionality achieved using "JSON for Modern C++": https://github.com/nlohmann/json. Related to #725. --- cmd/mrconvert.cpp | 24 + cmd/mrinfo.cpp | 29 +- docs/reference/commands/mrconvert.rst | 2 + docs/reference/commands/mrinfo.rst | 2 + lib/file/json.h | 10450 ++++++++++++++++++++++++ 5 files changed, 10506 insertions(+), 1 deletion(-) create mode 100644 lib/file/json.h diff --git a/cmd/mrconvert.cpp b/cmd/mrconvert.cpp index 2bb2572a4f..558c41445a 100644 --- a/cmd/mrconvert.cpp +++ b/cmd/mrconvert.cpp @@ -21,6 +21,7 @@ #include "algo/threaded_copy.h" #include "adapter/extract.h" #include "adapter/permute_axes.h" +#include "file/json.h" #include "dwi/gradient.h" @@ -77,6 +78,10 @@ void usage () "effect for floating-point and binary images.") + Argument ("values").type_sequence_float() + + Option ("json", + "import data from a JSON file into header key-value pairs") + + Argument ("file").type_file_in() + + Stride::Options + DataType::options() @@ -249,6 +254,25 @@ void run () } + opt = get_options ("json"); + if (opt.size()) { + std::ifstream in (opt[0][0]); + if (!in) + throw Exception ("Error opening JSON file \"" + std::string(opt[0][0]) + "\""); + nlohmann::json json; + try { + in >> json; + } catch (...) { + throw Exception ("Error parsing JSON file \"" + std::string(opt[0][0]) + "\""); + } + for (auto i = json.cbegin(); i != json.cend(); ++i) { + // Only load simple parameters at the first level + if (i->is_primitive()) + header_out.keyval().insert (std::make_pair (i.key(), str(i.value()))); + } + } + + opt = get_options ("scaling"); if (opt.size()) { if (header_out.datatype().is_integer()) { diff --git a/cmd/mrinfo.cpp b/cmd/mrinfo.cpp index 683f502285..7659fa8942 100644 --- a/cmd/mrinfo.cpp +++ b/cmd/mrinfo.cpp @@ -20,6 +20,7 @@ #include "command.h" #include "header.h" +#include "file/json.h" #include "dwi/gradient.h" @@ -76,6 +77,9 @@ void usage () "specified key (use 'all' to list all keys found)").allow_multiple() + Argument ("key").type_text() + + Option ("json", "export header key/value entries to a JSON file") + + Argument ("file").type_file_out() + + GradImportOptions + Option ("raw_dwgrad", "do not modify the gradient table from what was found in the image headers. This skips the " @@ -157,9 +161,11 @@ void run () bool export_grad = check_option_group (GradExportOptions); - if (export_grad && argument.size() > 1 ) + if (export_grad && argument.size() > 1) throw Exception ("can only export DW gradient table to file if a single input image is provided"); + std::unique_ptr json (get_options ("json").size() ? new nlohmann::json : nullptr); + if (get_options ("norealign").size()) Header::do_not_realign_transform = true; @@ -218,9 +224,30 @@ void run () DWI::export_grad_commandline (header); + if (json) { + for (const auto& kv : header.keyval()) { + if (json->find (kv.first) == json->end()) { + (*json)[kv.first] = kv.second; + } else if ((*json)[kv.first] != kv.second) { + // If the value for this key differs between images, turn the JSON entry into an array + if ((*json)[kv.first].is_array()) + (*json)[kv.first].push_back (kv.second); + else + (*json)[kv.first] = { (*json)[kv.first], kv.second }; + } + } + } + if (print_full_header) std::cout << header.description(); } + if (json) { + auto opt = get_options ("json"); + assert (opt.size()); + File::OFStream out (opt[0][0]); + out << json->dump(4) << "\n"; + } + } diff --git a/docs/reference/commands/mrconvert.rst b/docs/reference/commands/mrconvert.rst index 974f65ea2b..a2e9153035 100644 --- a/docs/reference/commands/mrconvert.rst +++ b/docs/reference/commands/mrconvert.rst @@ -31,6 +31,8 @@ Options - **-scaling values** specify the data scaling parameters used to rescale the intensity values. These take the form of a comma-separated 2-vector of floating-point values, corresponding to offset & scale, with final intensity values being given by offset + scale * stored_value. By default, the values in the input image header are passed through to the output image header when writing to an integer image, and reset to 0,1 (no scaling) for floating-point and binary images. Note that his option has no effect for floating-point and binary images. +- **-json file** import data from a JSON file into header key-value pairs + Stride options ^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/mrinfo.rst b/docs/reference/commands/mrinfo.rst index 3ae81b7b84..aeac0b3769 100644 --- a/docs/reference/commands/mrinfo.rst +++ b/docs/reference/commands/mrinfo.rst @@ -48,6 +48,8 @@ Options - **-property key** any text properties embedded in the image header under the specified key (use 'all' to list all keys found) +- **-json file** export header key/value entries to a JSON file + DW gradient table import options ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/lib/file/json.h b/lib/file/json.h new file mode 100644 index 0000000000..957ba094d7 --- /dev/null +++ b/lib/file/json.h @@ -0,0 +1,10450 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + /* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 2.0.2 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +Copyright (c) 2013-2016 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. +*/ + +#ifndef NLOHMANN_JSON_HPP +#define NLOHMANN_JSON_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// exclude unsupported compilers +#if defined(__clang__) + #define CLANG_VERSION (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) + #if CLANG_VERSION < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#elif defined(__GNUC__) + #define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) + #if GCC_VERSION < 40900 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#endif + +// disable float-equal warnings on GCC/clang +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ + + +/*! +@brief unnamed namespace with internal helper functions +@since version 1.0.0 +*/ +namespace +{ +/*! +@brief Helper to determine whether there's a key_type for T. + +Thus helper is used to tell associative containers apart from other containers +such as sequence containers. For instance, `std::map` passes the test as it +contains a `mapped_type`, whereas `std::vector` fails the test. + +@sa http://stackoverflow.com/a/7728728/266378 +@since version 1.0.0 +*/ +template +struct has_mapped_type +{ + private: + template static char test(typename C::mapped_type*); + template static char (&test(...))[2]; + public: + static constexpr bool value = sizeof(test(0)) == 1; +}; + +/*! +@brief helper class to create locales with decimal point + +This struct is used a default locale during the JSON serialization. JSON +requires the decimal point to be `.`, so this function overloads the +`do_decimal_point()` function to return `.`. This function is called by +float-to-string conversions to retrieve the decimal separator between integer +and fractional parts. + +@sa https://github.com/nlohmann/json/issues/51#issuecomment-86869315 +@since version 2.0.0 +*/ +struct DecimalSeparator : std::numpunct +{ + char do_decimal_point() const + { + return '.'; + } +}; + +} + +/*! +@brief a class to store JSON values + +@tparam ObjectType type for JSON objects (`std::map` by default; will be used +in @ref object_t) +@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used +in @ref array_t) +@tparam StringType type for JSON strings and object keys (`std::string` by +default; will be used in @ref string_t) +@tparam BooleanType type for JSON booleans (`bool` by default; will be used +in @ref boolean_t) +@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by +default; will be used in @ref number_integer_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c +`uint64_t` by default; will be used in @ref number_unsigned_t) +@tparam NumberFloatType type for JSON floating-point numbers (`double` by +default; will be used in @ref number_float_t) +@tparam AllocatorType type of the allocator to use (`std::allocator` by +default) + +@requirement The class satisfies the following concept requirements: +- Basic + - [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible): + JSON values can be default constructed. The result will be a JSON null value. + - [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible): + A JSON value can be constructed from an rvalue argument. + - [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible): + A JSON value can be copy-constructed from an lvalue expression. + - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable): + A JSON value van be assigned from an rvalue argument. + - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable): + A JSON value can be copy-assigned from an lvalue expression. + - [Destructible](http://en.cppreference.com/w/cpp/concept/Destructible): + JSON values can be destructed. +- Layout + - [StandardLayoutType](http://en.cppreference.com/w/cpp/concept/StandardLayoutType): + JSON values have + [standard layout](http://en.cppreference.com/w/cpp/language/data_members#Standard_layout): + All non-static data members are private and standard layout types, the class + has no virtual functions or (virtual) base classes. +- Library-wide + - [EqualityComparable](http://en.cppreference.com/w/cpp/concept/EqualityComparable): + JSON values can be compared with `==`, see @ref + operator==(const_reference,const_reference). + - [LessThanComparable](http://en.cppreference.com/w/cpp/concept/LessThanComparable): + JSON values can be compared with `<`, see @ref + operator<(const_reference,const_reference). + - [Swappable](http://en.cppreference.com/w/cpp/concept/Swappable): + Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of + other compatible types, using unqualified function call @ref swap(). + - [NullablePointer](http://en.cppreference.com/w/cpp/concept/NullablePointer): + JSON values can be compared against `std::nullptr_t` objects which are used + to model the `null` value. +- Container + - [Container](http://en.cppreference.com/w/cpp/concept/Container): + JSON values can be used like STL containers and provide iterator access. + - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer); + JSON values can be used like STL containers and provide reverse iterator + access. + +@invariant The member variables @a m_value and @a m_type have the following +relationship: +- If `m_type == value_t::object`, then `m_value.object != nullptr`. +- If `m_type == value_t::array`, then `m_value.array != nullptr`. +- If `m_type == value_t::string`, then `m_value.string != nullptr`. +The invariants are checked by member function assert_invariant(). + +@internal +@note ObjectType trick from http://stackoverflow.com/a/9860911 +@endinternal + +@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange +Format](http://rfc7159.net/rfc7159) + +@since version 1.0.0 + +@nosubgrouping +*/ +template < + template class ObjectType = std::map, + template class ArrayType = std::vector, + class StringType = std::string, + class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator + > +class basic_json +{ + private: + /// workaround type for MSVC + using basic_json_t = basic_json; + + public: + // forward declarations + template class json_reverse_iterator; + class json_pointer; + + ///////////////////// + // container types // + ///////////////////// + + /// @name container types + /// The canonic container types to use @ref basic_json like any other STL + /// container. + /// @{ + + /// the type of elements in a basic_json container + using value_type = basic_json; + + /// the type of an element reference + using reference = value_type&; + /// the type of an element const reference + using const_reference = const value_type&; + + /// a type to represent differences between iterators + using difference_type = std::ptrdiff_t; + /// a type to represent container sizes + using size_type = std::size_t; + + /// the allocator type + using allocator_type = AllocatorType; + + /// the type of an element pointer + using pointer = typename std::allocator_traits::pointer; + /// the type of an element const pointer + using const_pointer = typename std::allocator_traits::const_pointer; + + /// an iterator for a basic_json container + class iterator; + /// a const iterator for a basic_json container + class const_iterator; + /// a reverse iterator for a basic_json container + using reverse_iterator = json_reverse_iterator; + /// a const reverse iterator for a basic_json container + using const_reverse_iterator = json_reverse_iterator; + + /// @} + + + /*! + @brief returns the allocator associated with the container + */ + static allocator_type get_allocator() + { + return allocator_type(); + } + + + /////////////////////////// + // JSON value data types // + /////////////////////////// + + /// @name JSON value data types + /// The data types to store a JSON value. These types are derived from + /// the template arguments passed to class @ref basic_json. + /// @{ + + /*! + @brief a type for an object + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: + > An object is an unordered collection of zero or more name/value pairs, + > where a name is a string and a value is a string, number, boolean, null, + > object, or array. + + To store objects in C++, a type is defined by the template parameters + described below. + + @tparam ObjectType the container to store objects (e.g., `std::map` or + `std::unordered_map`) + @tparam StringType the type of the keys or names (e.g., `std::string`). + The comparison function `std::less` is used to order elements + inside the container. + @tparam AllocatorType the allocator to use for objects (e.g., + `std::allocator`) + + #### Default type + + With the default values for @a ObjectType (`std::map`), @a StringType + (`std::string`), and @a AllocatorType (`std::allocator`), the default + value for @a object_t is: + + @code {.cpp} + std::map< + std::string, // key_type + basic_json, // value_type + std::less, // key_compare + std::allocator> // allocator_type + > + @endcode + + #### Behavior + + The choice of @a object_t influences the behavior of the JSON class. With + the default type, objects have the following behavior: + + - When all names are unique, objects will be interoperable in the sense + that all software implementations receiving that object will agree on + the name-value mappings. + - When the names within an object are not unique, later stored name/value + pairs overwrite previously stored name/value pairs, leaving the used + names unique. For instance, `{"key": 1}` and `{"key": 2, "key": 1}` will + be treated as equal and both stored as `{"key": 1}`. + - Internally, name/value pairs are stored in lexicographical order of the + names. Objects will also be serialized (see @ref dump) in this order. + For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored + and serialized as `{"a": 2, "b": 1}`. + - When comparing objects, the order of the name/value pairs is irrelevant. + This makes objects interoperable in the sense that they will not be + affected by these differences. For instance, `{"b": 1, "a": 2}` and + `{"a": 2, "b": 1}` will be treated as equal. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the object's limit of nesting is not constraint explicitly. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON object. + + #### Storage + + Objects are stored as pointers in a @ref basic_json type. That is, for any + access to object values, a pointer of type `object_t*` must be + dereferenced. + + @sa @ref array_t -- type for an array value + + @since version 1.0.0 + + @note The order name/value pairs are added to the object is *not* + preserved by the library. Therefore, iterating an object may return + name/value pairs in a different order than they were originally stored. In + fact, keys will be traversed in alphabetical order as `std::map` with + `std::less` is used by default. Please note this behavior conforms to [RFC + 7159](http://rfc7159.net/rfc7159), because any order implements the + specified "unordered" nature of JSON objects. + */ + using object_t = ObjectType, + AllocatorType>>; + + /*! + @brief a type for an array + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: + > An array is an ordered sequence of zero or more values. + + To store objects in C++, a type is defined by the template parameters + explained below. + + @tparam ArrayType container type to store arrays (e.g., `std::vector` or + `std::list`) + @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) + + #### Default type + + With the default values for @a ArrayType (`std::vector`) and @a + AllocatorType (`std::allocator`), the default value for @a array_t is: + + @code {.cpp} + std::vector< + basic_json, // value_type + std::allocator // allocator_type + > + @endcode + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the array's limit of nesting is not constraint explicitly. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON array. + + #### Storage + + Arrays are stored as pointers in a @ref basic_json type. That is, for any + access to array values, a pointer of type `array_t*` must be dereferenced. + + @sa @ref object_t -- type for an object value + + @since version 1.0.0 + */ + using array_t = ArrayType>; + + /*! + @brief a type for a string + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: + > A string is a sequence of zero or more Unicode characters. + + To store objects in C++, a type is defined by the template parameter + described below. Unicode values are split by the JSON class into + byte-sized characters during deserialization. + + @tparam StringType the container to store strings (e.g., `std::string`). + Note this container is used for keys/names in objects, see @ref object_t. + + #### Default type + + With the default values for @a StringType (`std::string`), the default + value for @a string_t is: + + @code {.cpp} + std::string + @endcode + + #### String comparison + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > Software implementations are typically required to test names of object + > members for equality. Implementations that transform the textual + > representation into sequences of Unicode code units and then perform the + > comparison numerically, code unit by code unit, are interoperable in the + > sense that implementations will agree in all cases on equality or + > inequality of two strings. For example, implementations that compare + > strings with escaped characters unconverted may incorrectly find that + > `"a\\b"` and `"a\u005Cb"` are not equal. + + This implementation is interoperable as it does compare strings code unit + by code unit. + + #### Storage + + String values are stored as pointers in a @ref basic_json type. That is, + for any access to string values, a pointer of type `string_t*` must be + dereferenced. + + @since version 1.0.0 + */ + using string_t = StringType; + + /*! + @brief a type for a boolean + + [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a + type which differentiates the two literals `true` and `false`. + + To store objects in C++, a type is defined by the template parameter @a + BooleanType which chooses the type to use. + + #### Default type + + With the default values for @a BooleanType (`bool`), the default value for + @a boolean_t is: + + @code {.cpp} + bool + @endcode + + #### Storage + + Boolean values are stored directly inside a @ref basic_json type. + + @since version 1.0.0 + */ + using boolean_t = BooleanType; + + /*! + @brief a type for a number (integer) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store integer numbers in C++, a type is defined by the template + parameter @a NumberIntegerType which chooses the type to use. + + #### Default type + + With the default values for @a NumberIntegerType (`int64_t`), the default + value for @a number_integer_t is: + + @code {.cpp} + int64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `9223372036854775807` (INT64_MAX) and the minimal integer number + that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers + that are out of range will yield over/underflow when used in a + constructor. During deserialization, too large or small integer numbers + will be automatically be stored as @ref number_unsigned_t or @ref + number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange of the exactly supported range [INT64_MIN, + INT64_MAX], this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_integer_t = NumberIntegerType; + + /*! + @brief a type for a number (unsigned) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store unsigned integer numbers in C++, a type is defined by the + template parameter @a NumberUnsignedType which chooses the type to use. + + #### Default type + + With the default values for @a NumberUnsignedType (`uint64_t`), the + default value for @a number_unsigned_t is: + + @code {.cpp} + uint64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer + number that can be stored is `0`. Integer numbers that are out of range + will yield over/underflow when used in a constructor. During + deserialization, too large or small integer numbers will be automatically + be stored as @ref number_integer_t or @ref number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange (when considered in conjunction with the + number_integer_t type) of the exactly supported range [0, UINT64_MAX], + this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + @sa @ref number_integer_t -- type for number values (integer) + + @since version 2.0.0 + */ + using number_unsigned_t = NumberUnsignedType; + + /*! + @brief a type for a number (floating-point) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store floating-point numbers in C++, a type is defined by the template + parameter @a NumberFloatType which chooses the type to use. + + #### Default type + + With the default values for @a NumberFloatType (`double`), the default + value for @a number_float_t is: + + @code {.cpp} + double + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in floating-point literals will be ignored. Internally, + the value will be stored as decimal number. For instance, the C++ + floating-point literal `01.2` will be serialized to `1.2`. During + deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > This specification allows implementations to set limits on the range and + > precision of numbers accepted. Since software that implements IEEE + > 754-2008 binary64 (double precision) numbers is generally available and + > widely used, good interoperability can be achieved by implementations + > that expect no more precision or range than these provide, in the sense + > that implementations will approximate JSON numbers within the expected + > precision. + + This implementation does exactly follow this approach, as it uses double + precision floating-point numbers. Note values smaller than + `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` + will be stored as NaN internally and be serialized to `null`. + + #### Storage + + Floating-point number values are stored directly inside a @ref basic_json + type. + + @sa @ref number_integer_t -- type for number values (integer) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_float_t = NumberFloatType; + + /// @} + + + /////////////////////////// + // JSON type enumeration // + /////////////////////////// + + /*! + @brief the JSON type enumeration + + This enumeration collects the different JSON types. It is internally used + to distinguish the stored values, and the functions @ref is_null(), @ref + is_object(), @ref is_array(), @ref is_string(), @ref is_boolean(), @ref + is_number() (with @ref is_number_integer(), @ref is_number_unsigned(), and + @ref is_number_float()), @ref is_discarded(), @ref is_primitive(), and + @ref is_structured() rely on it. + + @note There are three enumeration entries (number_integer, + number_unsigned, and number_float), because the library distinguishes + these three types for numbers: @ref number_unsigned_t is used for unsigned + integers, @ref number_integer_t is used for signed integers, and @ref + number_float_t is used for floating-point numbers or to approximate + integers which do not fit in the limits of their respective type. + + @sa @ref basic_json(const value_t value_type) -- create a JSON value with + the default value for a given type + + @since version 1.0.0 + */ + enum class value_t : uint8_t + { + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + discarded ///< discarded by the the parser callback function + }; + + + private: + + /// helper for exception-safe object creation + template + static T* create(Args&& ... args) + { + AllocatorType alloc; + auto deleter = [&](T * object) + { + alloc.deallocate(object, 1); + }; + std::unique_ptr object(alloc.allocate(1), deleter); + alloc.construct(object.get(), std::forward(args)...); + assert(object.get() != nullptr); + return object.release(); + } + + //////////////////////// + // JSON value storage // + //////////////////////// + + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. This + union combines the different storage types for the JSON value types + defined in @ref value_t. + + JSON type | value_t type | used type + --------- | --------------- | ------------------------ + object | object | pointer to @ref object_t + array | array | pointer to @ref array_t + string | string | pointer to @ref string_t + boolean | boolean | @ref boolean_t + number | number_integer | @ref number_integer_t + number | number_unsigned | @ref number_unsigned_t + number | number_float | @ref number_float_t + null | null | *no value is stored* + + @note Variable-length types (objects, arrays, and strings) are stored as + pointers. The size of the union should not exceed 64 bits if the default + value types are used. + + @since version 1.0.0 + */ + union json_value + { + /// object (stored with pointer to save storage) + object_t* object; + /// array (stored with pointer to save storage) + array_t* array; + /// string (stored with pointer to save storage) + string_t* string; + /// boolean + boolean_t boolean; + /// number (integer) + number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; + /// number (floating-point) + number_float_t number_float; + + /// default constructor (for null values) + json_value() = default; + /// constructor for booleans + json_value(boolean_t v) noexcept : boolean(v) {} + /// constructor for numbers (integer) + json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + /// constructor for numbers (floating-point) + json_value(number_float_t v) noexcept : number_float(v) {} + /// constructor for empty values of a given type + json_value(value_t t) + { + switch (t) + { + case value_t::object: + { + object = create(); + break; + } + + case value_t::array: + { + array = create(); + break; + } + + case value_t::string: + { + string = create(""); + break; + } + + case value_t::boolean: + { + boolean = boolean_t(false); + break; + } + + case value_t::number_integer: + { + number_integer = number_integer_t(0); + break; + } + + case value_t::number_unsigned: + { + number_unsigned = number_unsigned_t(0); + break; + } + + case value_t::number_float: + { + number_float = number_float_t(0.0); + break; + } + + default: + { + break; + } + } + } + + /// constructor for strings + json_value(const string_t& value) + { + string = create(value); + } + + /// constructor for objects + json_value(const object_t& value) + { + object = create(value); + } + + /// constructor for arrays + json_value(const array_t& value) + { + array = create(value); + } + }; + + /*! + @brief checks the class invariants + + This function asserts the class invariants. It needs to be called at the + end of every constructor to make sure that created objects respect the + invariant. Furthermore, it has to be called each time the type of a JSON + value is changed, because the invariant expresses a relationship between + @a m_type and @a m_value. + */ + void assert_invariant() const + { + assert(m_type != value_t::object or m_value.object != nullptr); + assert(m_type != value_t::array or m_value.array != nullptr); + assert(m_type != value_t::string or m_value.string != nullptr); + } + + public: + ////////////////////////// + // JSON parser callback // + ////////////////////////// + + /*! + @brief JSON callback events + + This enumeration lists the parser events that can trigger calling a + callback function of type @ref parser_callback_t during parsing. + + @image html callback_events.png "Example when certain parse events are triggered" + + @since version 1.0.0 + */ + enum class parse_event_t : uint8_t + { + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value + }; + + /*! + @brief per-element parser callback type + + With a parser callback function, the result of parsing a JSON text can be + influenced. When passed to @ref parse(std::istream&, const + parser_callback_t) or @ref parse(const string_t&, const parser_callback_t), + it is called on certain events (passed as @ref parse_event_t via parameter + @a event) with a set recursion depth @a depth and context JSON value + @a parsed. The return value of the callback function is a boolean + indicating whether the element that emitted the callback shall be kept or + not. + + We distinguish six scenarios (determined by the event type) in which the + callback function can be called. The following table describes the values + of the parameters @a depth, @a event, and @a parsed. + + parameter @a event | description | parameter @a depth | parameter @a parsed + ------------------ | ----------- | ------------------ | ------------------- + parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded + parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key + parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object + parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded + parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array + parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + Discarding a value (i.e., returning `false`) has different effects + depending on the context in which function was called: + + - Discarded values in structured types are skipped. That is, the parser + will behave as if the discarded value was never read. + - In case a value outside a structured type is skipped, it is replaced + with `null`. This case happens if the top-level element is skipped. + + @param[in] depth the depth of the recursion during parsing + + @param[in] event an event of type parse_event_t indicating the context in + the callback function has been called + + @param[in,out] parsed the current intermediate parse result; note that + writing to this value has no effect for parse_event_t::key events + + @return Whether the JSON value which called the function during parsing + should be kept (`true`) or not (`false`). In the latter case, it is either + skipped completely or replaced by an empty discarded object. + + @sa @ref parse(std::istream&, parser_callback_t) or + @ref parse(const string_t&, parser_callback_t) for examples + + @since version 1.0.0 + */ + using parser_callback_t = std::function; + + + ////////////////// + // constructors // + ////////////////// + + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ + + /*! + @brief create an empty value with a given type + + Create an empty JSON value with a given type. The value will be default + initialized with an empty value which depends on the type: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @param[in] value_type the type of the value to create + + @complexity Constant. + + @throw std::bad_alloc if allocation for object, array, or string value + fails + + @liveexample{The following code shows the constructor for different @ref + value_t values,basic_json__value_t} + + @sa @ref basic_json(std::nullptr_t) -- create a `null` value + @sa @ref basic_json(boolean_t value) -- create a boolean value + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const object_t&) -- create a object value + @sa @ref basic_json(const array_t&) -- create a array value + @sa @ref basic_json(const number_float_t) -- create a number + (floating-point) value + @sa @ref basic_json(const number_integer_t) -- create a number (integer) + value + @sa @ref basic_json(const number_unsigned_t) -- create a number (unsigned) + value + + @since version 1.0.0 + */ + basic_json(const value_t value_type) + : m_type(value_type), m_value(value_type) + { + assert_invariant(); + } + + /*! + @brief create a null object (implicitly) + + Create a `null` JSON value. This is the implicit version of the `null` + value constructor as it takes no parameters. + + @note The class invariant is satisfied, because it poses no requirements + for null values. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - As postcondition, it holds: `basic_json().empty() == true`. + + @liveexample{The following code shows the constructor for a `null` JSON + value.,basic_json} + + @sa @ref basic_json(std::nullptr_t) -- create a `null` value + + @since version 1.0.0 + */ + basic_json() = default; + + /*! + @brief create a null object (explicitly) + + Create a `null` JSON value. This is the explicitly version of the `null` + value constructor as it takes a null pointer as parameter. It allows to + create `null` values by explicitly assigning a `nullptr` to a JSON value. + The passed null pointer itself is not read -- it is only used to choose + the right constructor. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @liveexample{The following code shows the constructor with null pointer + parameter.,basic_json__nullptr_t} + + @sa @ref basic_json() -- default constructor (implicitly creating a `null` + value) + + @since version 1.0.0 + */ + basic_json(std::nullptr_t) noexcept + : basic_json(value_t::null) + { + assert_invariant(); + } + + /*! + @brief create an object (explicit) + + Create an object JSON value with a given content. + + @param[in] val a value for the object + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for object value fails + + @liveexample{The following code shows the constructor with an @ref + object_t parameter.,basic_json__object_t} + + @sa @ref basic_json(const CompatibleObjectType&) -- create an object value + from a compatible STL container + + @since version 1.0.0 + */ + basic_json(const object_t& val) + : m_type(value_t::object), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create an object (implicit) + + Create an object JSON value with a given content. This constructor allows + any type @a CompatibleObjectType that can be used to construct values of + type @ref object_t. + + @tparam CompatibleObjectType An object type whose `key_type` and + `value_type` is compatible to @ref object_t. Examples include `std::map`, + `std::unordered_map`, `std::multimap`, and `std::unordered_multimap` with + a `key_type` of `std::string`, and a `value_type` from which a @ref + basic_json value can be constructed. + + @param[in] val a value for the object + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for object value fails + + @liveexample{The following code shows the constructor with several + compatible object type parameters.,basic_json__CompatibleObjectType} + + @sa @ref basic_json(const object_t&) -- create an object value + + @since version 1.0.0 + */ + template ::value and + std::is_constructible::value, int>::type + = 0> + basic_json(const CompatibleObjectType& val) + : m_type(value_t::object) + { + using std::begin; + using std::end; + m_value.object = create(begin(val), end(val)); + assert_invariant(); + } + + /*! + @brief create an array (explicit) + + Create an array JSON value with a given content. + + @param[in] val a value for the array + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for array value fails + + @liveexample{The following code shows the constructor with an @ref array_t + parameter.,basic_json__array_t} + + @sa @ref basic_json(const CompatibleArrayType&) -- create an array value + from a compatible STL containers + + @since version 1.0.0 + */ + basic_json(const array_t& val) + : m_type(value_t::array), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create an array (implicit) + + Create an array JSON value with a given content. This constructor allows + any type @a CompatibleArrayType that can be used to construct values of + type @ref array_t. + + @tparam CompatibleArrayType An object type whose `value_type` is + compatible to @ref array_t. Examples include `std::vector`, `std::deque`, + `std::list`, `std::forward_list`, `std::array`, `std::set`, + `std::unordered_set`, `std::multiset`, and `unordered_multiset` with a + `value_type` from which a @ref basic_json value can be constructed. + + @param[in] val a value for the array + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for array value fails + + @liveexample{The following code shows the constructor with several + compatible array type parameters.,basic_json__CompatibleArrayType} + + @sa @ref basic_json(const array_t&) -- create an array value + + @since version 1.0.0 + */ + template ::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + std::is_constructible::value, int>::type + = 0> + basic_json(const CompatibleArrayType& val) + : m_type(value_t::array) + { + using std::begin; + using std::end; + m_value.array = create(begin(val), end(val)); + assert_invariant(); + } + + /*! + @brief create a string (explicit) + + Create an string JSON value with a given content. + + @param[in] val a value for the string + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for string value fails + + @liveexample{The following code shows the constructor with an @ref + string_t parameter.,basic_json__string_t} + + @sa @ref basic_json(const typename string_t::value_type*) -- create a + string value from a character pointer + @sa @ref basic_json(const CompatibleStringType&) -- create a string value + from a compatible string container + + @since version 1.0.0 + */ + basic_json(const string_t& val) + : m_type(value_t::string), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create a string (explicit) + + Create a string JSON value with a given content. + + @param[in] val a literal value for the string + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for string value fails + + @liveexample{The following code shows the constructor with string literal + parameter.,basic_json__string_t_value_type} + + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const CompatibleStringType&) -- create a string value + from a compatible string container + + @since version 1.0.0 + */ + basic_json(const typename string_t::value_type* val) + : basic_json(string_t(val)) + { + assert_invariant(); + } + + /*! + @brief create a string (implicit) + + Create a string JSON value with a given content. + + @param[in] val a value for the string + + @tparam CompatibleStringType an string type which is compatible to @ref + string_t, for instance `std::string`. + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for string value fails + + @liveexample{The following code shows the construction of a string value + from a compatible type.,basic_json__CompatibleStringType} + + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const typename string_t::value_type*) -- create a + string value from a character pointer + + @since version 1.0.0 + */ + template ::value, int>::type + = 0> + basic_json(const CompatibleStringType& val) + : basic_json(string_t(val)) + { + assert_invariant(); + } + + /*! + @brief create a boolean (explicit) + + Creates a JSON boolean type from a given value. + + @param[in] val a boolean value to store + + @complexity Constant. + + @liveexample{The example below demonstrates boolean + values.,basic_json__boolean_t} + + @since version 1.0.0 + */ + basic_json(boolean_t val) noexcept + : m_type(value_t::boolean), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create an integer number (explicit) + + Create an integer number JSON value with a given content. + + @tparam T A helper type to remove this function via SFINAE in case @ref + number_integer_t is the same as `int`. In this case, this constructor + would have the same signature as @ref basic_json(const int value). Note + the helper type @a T is not visible in this constructor's interface. + + @param[in] val an integer to create a JSON number from + + @complexity Constant. + + @liveexample{The example below shows the construction of an integer + number value.,basic_json__number_integer_t} + + @sa @ref basic_json(const int) -- create a number value (integer) + @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number + value (integer) from a compatible number type + + @since version 1.0.0 + */ + template::value) + and std::is_same::value + , int>::type + = 0> + basic_json(const number_integer_t val) noexcept + : m_type(value_t::number_integer), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create an integer number from an enum type (explicit) + + Create an integer number JSON value with a given content. + + @param[in] val an integer to create a JSON number from + + @note This constructor allows to pass enums directly to a constructor. As + C++ has no way of specifying the type of an anonymous enum explicitly, we + can only rely on the fact that such values implicitly convert to int. As + int may already be the same type of number_integer_t, we may need to + switch off the constructor @ref basic_json(const number_integer_t). + + @complexity Constant. + + @liveexample{The example below shows the construction of an integer + number value from an anonymous enum.,basic_json__const_int} + + @sa @ref basic_json(const number_integer_t) -- create a number value + (integer) + @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number + value (integer) from a compatible number type + + @since version 1.0.0 + */ + basic_json(const int val) noexcept + : m_type(value_t::number_integer), + m_value(static_cast(val)) + { + assert_invariant(); + } + + /*! + @brief create an integer number (implicit) + + Create an integer number JSON value with a given content. This constructor + allows any type @a CompatibleNumberIntegerType that can be used to + construct values of type @ref number_integer_t. + + @tparam CompatibleNumberIntegerType An integer type which is compatible to + @ref number_integer_t. Examples include the types `int`, `int32_t`, + `long`, and `short`. + + @param[in] val an integer to create a JSON number from + + @complexity Constant. + + @liveexample{The example below shows the construction of several integer + number values from compatible + types.,basic_json__CompatibleIntegerNumberType} + + @sa @ref basic_json(const number_integer_t) -- create a number value + (integer) + @sa @ref basic_json(const int) -- create a number value (integer) + + @since version 1.0.0 + */ + template::value and + std::numeric_limits::is_integer and + std::numeric_limits::is_signed, + CompatibleNumberIntegerType>::type + = 0> + basic_json(const CompatibleNumberIntegerType val) noexcept + : m_type(value_t::number_integer), + m_value(static_cast(val)) + { + assert_invariant(); + } + + /*! + @brief create an unsigned integer number (explicit) + + Create an unsigned integer number JSON value with a given content. + + @tparam T helper type to compare number_unsigned_t and unsigned int (not + visible in) the interface. + + @param[in] val an integer to create a JSON number from + + @complexity Constant. + + @sa @ref basic_json(const CompatibleNumberUnsignedType) -- create a number + value (unsigned integer) from a compatible number type + + @since version 2.0.0 + */ + template::value) + and std::is_same::value + , int>::type + = 0> + basic_json(const number_unsigned_t val) noexcept + : m_type(value_t::number_unsigned), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create an unsigned number (implicit) + + Create an unsigned number JSON value with a given content. This + constructor allows any type @a CompatibleNumberUnsignedType that can be + used to construct values of type @ref number_unsigned_t. + + @tparam CompatibleNumberUnsignedType An integer type which is compatible + to @ref number_unsigned_t. Examples may include the types `unsigned int`, + `uint32_t`, or `unsigned short`. + + @param[in] val an unsigned integer to create a JSON number from + + @complexity Constant. + + @sa @ref basic_json(const number_unsigned_t) -- create a number value + (unsigned) + + @since version 2.0.0 + */ + template ::value and + std::numeric_limits::is_integer and + not std::numeric_limits::is_signed, + CompatibleNumberUnsignedType>::type + = 0> + basic_json(const CompatibleNumberUnsignedType val) noexcept + : m_type(value_t::number_unsigned), + m_value(static_cast(val)) + { + assert_invariant(); + } + + /*! + @brief create a floating-point number (explicit) + + Create a floating-point number JSON value with a given content. + + @param[in] val a floating-point value to create a JSON number from + + @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 + disallows NaN values: + > Numeric values that cannot be represented in the grammar below (such as + > Infinity and NaN) are not permitted. + In case the parameter @a val is not a number, a JSON null value is created + instead. + + @complexity Constant. + + @liveexample{The following example creates several floating-point + values.,basic_json__number_float_t} + + @sa @ref basic_json(const CompatibleNumberFloatType) -- create a number + value (floating-point) from a compatible number type + + @since version 1.0.0 + */ + basic_json(const number_float_t val) noexcept + : m_type(value_t::number_float), m_value(val) + { + // replace infinity and NAN by null + if (not std::isfinite(val)) + { + m_type = value_t::null; + m_value = json_value(); + } + + assert_invariant(); + } + + /*! + @brief create an floating-point number (implicit) + + Create an floating-point number JSON value with a given content. This + constructor allows any type @a CompatibleNumberFloatType that can be used + to construct values of type @ref number_float_t. + + @tparam CompatibleNumberFloatType A floating-point type which is + compatible to @ref number_float_t. Examples may include the types `float` + or `double`. + + @param[in] val a floating-point to create a JSON number from + + @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 + disallows NaN values: + > Numeric values that cannot be represented in the grammar below (such as + > Infinity and NaN) are not permitted. + In case the parameter @a val is not a number, a JSON null value is + created instead. + + @complexity Constant. + + @liveexample{The example below shows the construction of several + floating-point number values from compatible + types.,basic_json__CompatibleNumberFloatType} + + @sa @ref basic_json(const number_float_t) -- create a number value + (floating-point) + + @since version 1.0.0 + */ + template::value and + std::is_floating_point::value>::type + > + basic_json(const CompatibleNumberFloatType val) noexcept + : basic_json(number_float_t(val)) + { + assert_invariant(); + } + + /*! + @brief create a container (array or object) from an initializer list + + Creates a JSON value of type array or object from the passed initializer + list @a init. In case @a type_deduction is `true` (default), the type of + the JSON value to be created is deducted from the initializer list @a init + according to the following rules: + + 1. If the list is empty, an empty JSON object value `{}` is created. + 2. If the list consists of pairs whose first element is a string, a JSON + object value is created where the first elements of the pairs are + treated as keys and the second elements are as values. + 3. In all other cases, an array is created. + + The rules aim to create the best fit between a C++ initializer list and + JSON values. The rationale is as follows: + + 1. The empty initializer list is written as `{}` which is exactly an empty + JSON object. + 2. C++ has now way of describing mapped types other than to list a list of + pairs. As JSON requires that keys must be of type string, rule 2 is the + weakest constraint one can pose on initializer lists to interpret them + as an object. + 3. In all other cases, the initializer list could not be interpreted as + JSON object type, so interpreting it as JSON array type is safe. + + With the rules described above, the following JSON values cannot be + expressed by an initializer list: + + - the empty array (`[]`): use @ref array(std::initializer_list) + with an empty initializer list in this case + - arrays whose elements satisfy rule 2: use @ref + array(std::initializer_list) with the same initializer list + in this case + + @note When used without parentheses around an empty initializer list, @ref + basic_json() is called instead of this function, yielding the JSON null + value. + + @param[in] init initializer list with JSON values + + @param[in] type_deduction internal parameter; when set to `true`, the type + of the JSON value is deducted from the initializer list @a init; when set + to `false`, the type provided via @a manual_type is forced. This mode is + used by the functions @ref array(std::initializer_list) and + @ref object(std::initializer_list). + + @param[in] manual_type internal parameter; when @a type_deduction is set + to `false`, the created JSON value will use the provided type (only @ref + value_t::array and @ref value_t::object are valid); when @a type_deduction + is set to `true`, this parameter has no effect + + @throw std::domain_error if @a type_deduction is `false`, @a manual_type + is `value_t::object`, but @a init contains an element which is not a pair + whose first element is a string; example: `"cannot create object from + initializer list"` + + @complexity Linear in the size of the initializer list @a init. + + @liveexample{The example below shows how JSON values are created from + initializer lists.,basic_json__list_init_t} + + @sa @ref array(std::initializer_list) -- create a JSON array + value from an initializer list + @sa @ref object(std::initializer_list) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + basic_json(std::initializer_list init, + bool type_deduction = true, + value_t manual_type = value_t::array) + { + // check if each element is an array with two elements whose first + // element is a string + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const basic_json & element) + { + return element.is_array() and element.size() == 2 and element[0].is_string(); + }); + + // adjust type if type deduction is not wanted + if (not type_deduction) + { + // if array is wanted, do not create an object though possible + if (manual_type == value_t::array) + { + is_an_object = false; + } + + // if object is wanted but impossible, throw an exception + if (manual_type == value_t::object and not is_an_object) + { + throw std::domain_error("cannot create object from initializer list"); + } + } + + if (is_an_object) + { + // the initializer list is a list of pairs -> create object + m_type = value_t::object; + m_value = value_t::object; + + std::for_each(init.begin(), init.end(), [this](const basic_json & element) + { + m_value.object->emplace(*(element[0].m_value.string), element[1]); + }); + } + else + { + // the initializer list describes an array -> create array + m_type = value_t::array; + m_value.array = create(init); + } + + assert_invariant(); + } + + /*! + @brief explicitly create an array from an initializer list + + Creates a JSON array value from a given initializer list. That is, given a + list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the + initializer list is empty, the empty array `[]` is created. + + @note This function is only needed to express two edge cases that cannot + be realized with the initializer list constructor (@ref + basic_json(std::initializer_list, bool, value_t)). These cases + are: + 1. creating an array whose elements are all pairs whose first element is a + string -- in this case, the initializer list constructor would create an + object, taking the first elements as keys + 2. creating an empty array -- passing the empty initializer list to the + initializer list constructor yields an empty object + + @param[in] init initializer list with JSON values to create an array from + (optional) + + @return JSON array value + + @complexity Linear in the size of @a init. + + @liveexample{The following code shows an example for the `array` + function.,array} + + @sa @ref basic_json(std::initializer_list, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref object(std::initializer_list) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + static basic_json array(std::initializer_list init = + std::initializer_list()) + { + return basic_json(init, false, value_t::array); + } + + /*! + @brief explicitly create an object from an initializer list + + Creates a JSON object value from a given initializer list. The initializer + lists elements must be pairs, and their first elements must be strings. If + the initializer list is empty, the empty object `{}` is created. + + @note This function is only added for symmetry reasons. In contrast to the + related function @ref array(std::initializer_list), there are + no cases which can only be expressed by this function. That is, any + initializer list @a init can also be passed to the initializer list + constructor @ref basic_json(std::initializer_list, bool, + value_t). + + @param[in] init initializer list to create an object from (optional) + + @return JSON object value + + @throw std::domain_error if @a init is not a pair whose first elements are + strings; thrown by + @ref basic_json(std::initializer_list, bool, value_t) + + @complexity Linear in the size of @a init. + + @liveexample{The following code shows an example for the `object` + function.,object} + + @sa @ref basic_json(std::initializer_list, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref array(std::initializer_list) -- create a JSON array + value from an initializer list + + @since version 1.0.0 + */ + static basic_json object(std::initializer_list init = + std::initializer_list()) + { + return basic_json(init, false, value_t::object); + } + + /*! + @brief construct an array with count copies of given value + + Constructs a JSON array value by creating @a cnt copies of a passed value. + In case @a cnt is `0`, an empty array is created. As postcondition, + `std::distance(begin(),end()) == cnt` holds. + + @param[in] cnt the number of JSON copies of @a val to create + @param[in] val the JSON value to copy + + @complexity Linear in @a cnt. + + @liveexample{The following code shows examples for the @ref + basic_json(size_type\, const basic_json&) + constructor.,basic_json__size_type_basic_json} + + @since version 1.0.0 + */ + basic_json(size_type cnt, const basic_json& val) + : m_type(value_t::array) + { + m_value.array = create(cnt, val); + assert_invariant(); + } + + /*! + @brief construct a JSON container given an iterator range + + Constructs the JSON value with the contents of the range `[first, last)`. + The semantics depends on the different types a JSON value can have: + - In case of primitive types (number, boolean, or string), @a first must + be `begin()` and @a last must be `end()`. In this case, the value is + copied. Otherwise, std::out_of_range is thrown. + - In case of structured types (array, object), the constructor behaves as + similar versions for `std::vector`. + - In case of a null type, std::domain_error is thrown. + + @tparam InputIT an input iterator type (@ref iterator or @ref + const_iterator) + + @param[in] first begin of the range to copy from (included) + @param[in] last end of the range to copy from (excluded) + + @pre Iterators @a first and @a last must be initialized. + + @throw std::domain_error if iterators are not compatible; that is, do not + belong to the same JSON value; example: `"iterators are not compatible"` + @throw std::out_of_range if iterators are for a primitive type (number, + boolean, or string) where an out of range error can be detected easily; + example: `"iterators out of range"` + @throw std::bad_alloc if allocation for object, array, or string fails + @throw std::domain_error if called with a null value; example: `"cannot + use construct with iterators from null"` + + @complexity Linear in distance between @a first and @a last. + + @liveexample{The example below shows several ways to create JSON values by + specifying a subrange with iterators.,basic_json__InputIt_InputIt} + + @since version 1.0.0 + */ + template ::value or + std::is_same::value + , int>::type + = 0> + basic_json(InputIT first, InputIT last) + { + assert(first.m_object != nullptr); + assert(last.m_object != nullptr); + + // make sure iterator fits the current value + if (first.m_object != last.m_object) + { + throw std::domain_error("iterators are not compatible"); + } + + // copy type from first iterator + m_type = first.m_object->m_type; + + // check if iterator range is complete for primitive values + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) + { + throw std::out_of_range("iterators out of range"); + } + break; + } + + default: + { + break; + } + } + + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = first.m_object->m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = first.m_object->m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value.number_float = first.m_object->m_value.number_float; + break; + } + + case value_t::boolean: + { + m_value.boolean = first.m_object->m_value.boolean; + break; + } + + case value_t::string: + { + m_value = *first.m_object->m_value.string; + break; + } + + case value_t::object: + { + m_value.object = create(first.m_it.object_iterator, last.m_it.object_iterator); + break; + } + + case value_t::array: + { + m_value.array = create(first.m_it.array_iterator, last.m_it.array_iterator); + break; + } + + default: + { + throw std::domain_error("cannot use construct with iterators from " + first.m_object->type_name()); + } + } + + assert_invariant(); + } + + /*! + @brief construct a JSON value given an input stream + + @param[in,out] i stream to read a serialized JSON value from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates constructing a JSON value from + a `std::stringstream` with and without callback + function.,basic_json__istream} + + @since version 2.0.0 + */ + explicit basic_json(std::istream& i, const parser_callback_t cb = nullptr) + { + *this = parser(i, cb).parse(); + assert_invariant(); + } + + /////////////////////////////////////// + // other constructors and destructor // + /////////////////////////////////////// + + /*! + @brief copy constructor + + Creates a copy of a given JSON value. + + @param[in] other the JSON value to copy + + @complexity Linear in the size of @a other. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - As postcondition, it holds: `other == basic_json(other)`. + + @throw std::bad_alloc if allocation for object, array, or string fails. + + @liveexample{The following code shows an example for the copy + constructor.,basic_json__basic_json} + + @since version 1.0.0 + */ + basic_json(const basic_json& other) + : m_type(other.m_type) + { + // check of passed value is valid + other.assert_invariant(); + + switch (m_type) + { + case value_t::object: + { + m_value = *other.m_value.object; + break; + } + + case value_t::array: + { + m_value = *other.m_value.array; + break; + } + + case value_t::string: + { + m_value = *other.m_value.string; + break; + } + + case value_t::boolean: + { + m_value = other.m_value.boolean; + break; + } + + case value_t::number_integer: + { + m_value = other.m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value = other.m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value = other.m_value.number_float; + break; + } + + default: + { + break; + } + } + + assert_invariant(); + } + + /*! + @brief move constructor + + Move constructor. Constructs a JSON value with the contents of the given + value @a other using move semantics. It "steals" the resources from @a + other and leaves it as JSON null value. + + @param[in,out] other value to move to this object + + @post @a other is a JSON null value + + @complexity Constant. + + @liveexample{The code below shows the move constructor explicitly called + via std::move.,basic_json__moveconstructor} + + @since version 1.0.0 + */ + basic_json(basic_json&& other) noexcept + : m_type(std::move(other.m_type)), + m_value(std::move(other.m_value)) + { + // check that passed value is valid + other.assert_invariant(); + + // invalidate payload + other.m_type = value_t::null; + other.m_value = {}; + + assert_invariant(); + } + + /*! + @brief copy assignment + + Copy assignment operator. Copies a JSON value via the "copy and swap" + strategy: It is expressed in terms of the copy constructor, destructor, + and the swap() member function. + + @param[in] other value to copy from + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + + @liveexample{The code below shows and example for the copy assignment. It + creates a copy of value `a` which is then swapped with `b`. Finally\, the + copy of `a` (which is the null value after the swap) is + destroyed.,basic_json__copyassignment} + + @since version 1.0.0 + */ + reference& operator=(basic_json other) noexcept ( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + // check that passed value is valid + other.assert_invariant(); + + using std::swap; + swap(m_type, other.m_type); + swap(m_value, other.m_value); + + assert_invariant(); + return *this; + } + + /*! + @brief destructor + + Destroys the JSON value and frees all allocated memory. + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - All stored elements are destroyed and all memory is freed. + + @since version 1.0.0 + */ + ~basic_json() + { + assert_invariant(); + + switch (m_type) + { + case value_t::object: + { + AllocatorType alloc; + alloc.destroy(m_value.object); + alloc.deallocate(m_value.object, 1); + break; + } + + case value_t::array: + { + AllocatorType alloc; + alloc.destroy(m_value.array); + alloc.deallocate(m_value.array, 1); + break; + } + + case value_t::string: + { + AllocatorType alloc; + alloc.destroy(m_value.string); + alloc.deallocate(m_value.string, 1); + break; + } + + default: + { + // all other types need no specific destructor + break; + } + } + } + + /// @} + + public: + /////////////////////// + // object inspection // + /////////////////////// + + /// @name object inspection + /// Functions to inspect the type of a JSON value. + /// @{ + + /*! + @brief serialization + + Serialization function for JSON values. The function tries to mimic + Python's `json.dumps()` function, and currently supports its @a indent + parameter. + + @param[in] indent If indent is nonnegative, then array elements and object + members will be pretty-printed with that indent level. An indent level of + `0` will only insert newlines. `-1` (the default) selects the most compact + representation. + + @return string containing the serialization of the JSON value + + @complexity Linear. + + @liveexample{The following example shows the effect of different @a indent + parameters to the result of the serialization.,dump} + + @see https://docs.python.org/2/library/json.html#json.dump + + @since version 1.0.0 + */ + string_t dump(const int indent = -1) const + { + std::stringstream ss; + // fix locale problems + ss.imbue(std::locale(std::locale(), new DecimalSeparator)); + + // 6, 15 or 16 digits of precision allows round-trip IEEE 754 + // string->float->string, string->double->string or string->long + // double->string; to be safe, we read this value from + // std::numeric_limits::digits10 + ss.precision(std::numeric_limits::digits10); + + if (indent >= 0) + { + dump(ss, true, static_cast(indent)); + } + else + { + dump(ss, false, 0); + } + + return ss.str(); + } + + /*! + @brief return the type of the JSON value (explicit) + + Return the type of the JSON value as a value from the @ref value_t + enumeration. + + @return the type of the JSON value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `type()` for all JSON + types.,type} + + @since version 1.0.0 + */ + constexpr value_t type() const noexcept + { + return m_type; + } + + /*! + @brief return whether type is primitive + + This function returns true iff the JSON type is primitive (string, number, + boolean, or null). + + @return `true` if type is primitive (string, number, boolean, or null), + `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_primitive()` for all JSON + types.,is_primitive} + + @sa @ref is_structured() -- returns whether JSON value is structured + @sa @ref is_null() -- returns whether JSON value is `null` + @sa @ref is_string() -- returns whether JSON value is a string + @sa @ref is_boolean() -- returns whether JSON value is a boolean + @sa @ref is_number() -- returns whether JSON value is a number + + @since version 1.0.0 + */ + constexpr bool is_primitive() const noexcept + { + return is_null() or is_string() or is_boolean() or is_number(); + } + + /*! + @brief return whether type is structured + + This function returns true iff the JSON type is structured (array or + object). + + @return `true` if type is structured (array or object), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_structured()` for all JSON + types.,is_structured} + + @sa @ref is_primitive() -- returns whether value is primitive + @sa @ref is_array() -- returns whether value is an array + @sa @ref is_object() -- returns whether value is an object + + @since version 1.0.0 + */ + constexpr bool is_structured() const noexcept + { + return is_array() or is_object(); + } + + /*! + @brief return whether value is null + + This function returns true iff the JSON value is null. + + @return `true` if type is null, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_null()` for all JSON + types.,is_null} + + @since version 1.0.0 + */ + constexpr bool is_null() const noexcept + { + return m_type == value_t::null; + } + + /*! + @brief return whether value is a boolean + + This function returns true iff the JSON value is a boolean. + + @return `true` if type is boolean, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_boolean()` for all JSON + types.,is_boolean} + + @since version 1.0.0 + */ + constexpr bool is_boolean() const noexcept + { + return m_type == value_t::boolean; + } + + /*! + @brief return whether value is a number + + This function returns true iff the JSON value is a number. This includes + both integer and floating-point values. + + @return `true` if type is number (regardless whether integer, unsigned + integer or floating-type), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number()` for all JSON + types.,is_number} + + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number() const noexcept + { + return is_number_integer() or is_number_float(); + } + + /*! + @brief return whether value is an integer number + + This function returns true iff the JSON value is an integer or unsigned + integer number. This excludes floating-point values. + + @return `true` if type is an integer or unsigned integer number, `false` + otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_integer()` for all + JSON types.,is_number_integer} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number_integer() const noexcept + { + return m_type == value_t::number_integer or m_type == value_t::number_unsigned; + } + + /*! + @brief return whether value is an unsigned integer number + + This function returns true iff the JSON value is an unsigned integer + number. This excludes floating-point and (signed) integer values. + + @return `true` if type is an unsigned integer number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_unsigned()` for all + JSON types.,is_number_unsigned} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 2.0.0 + */ + constexpr bool is_number_unsigned() const noexcept + { + return m_type == value_t::number_unsigned; + } + + /*! + @brief return whether value is a floating-point number + + This function returns true iff the JSON value is a floating-point number. + This excludes integer and unsigned integer values. + + @return `true` if type is a floating-point number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_float()` for all + JSON types.,is_number_float} + + @sa @ref is_number() -- check if value is number + @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + + @since version 1.0.0 + */ + constexpr bool is_number_float() const noexcept + { + return m_type == value_t::number_float; + } + + /*! + @brief return whether value is an object + + This function returns true iff the JSON value is an object. + + @return `true` if type is object, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_object()` for all JSON + types.,is_object} + + @since version 1.0.0 + */ + constexpr bool is_object() const noexcept + { + return m_type == value_t::object; + } + + /*! + @brief return whether value is an array + + This function returns true iff the JSON value is an array. + + @return `true` if type is array, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_array()` for all JSON + types.,is_array} + + @since version 1.0.0 + */ + constexpr bool is_array() const noexcept + { + return m_type == value_t::array; + } + + /*! + @brief return whether value is a string + + This function returns true iff the JSON value is a string. + + @return `true` if type is string, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_string()` for all JSON + types.,is_string} + + @since version 1.0.0 + */ + constexpr bool is_string() const noexcept + { + return m_type == value_t::string; + } + + /*! + @brief return whether value is discarded + + This function returns true iff the JSON value was discarded during parsing + with a callback function (see @ref parser_callback_t). + + @note This function will always be `false` for JSON values after parsing. + That is, discarded values can only occur during parsing, but will be + removed when inside a structured value or replaced by null in other cases. + + @return `true` if type is discarded, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_discarded()` for all JSON + types.,is_discarded} + + @since version 1.0.0 + */ + constexpr bool is_discarded() const noexcept + { + return m_type == value_t::discarded; + } + + /*! + @brief return the type of the JSON value (implicit) + + Implicitly return the type of the JSON value as a value from the @ref + value_t enumeration. + + @return the type of the JSON value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies the @ref value_t operator for + all JSON types.,operator__value_t} + + @since version 1.0.0 + */ + constexpr operator value_t() const noexcept + { + return m_type; + } + + /// @} + + private: + ////////////////// + // value access // + ////////////////// + + /// get an object (explicit) + template ::value and + std::is_convertible::value + , int>::type = 0> + T get_impl(T*) const + { + if (is_object()) + { + return T(m_value.object->begin(), m_value.object->end()); + } + else + { + throw std::domain_error("type must be object, but is " + type_name()); + } + } + + /// get an object (explicit) + object_t get_impl(object_t*) const + { + if (is_object()) + { + return *(m_value.object); + } + else + { + throw std::domain_error("type must be object, but is " + type_name()); + } + } + + /// get an array (explicit) + template ::value and + not std::is_same::value and + not std::is_arithmetic::value and + not std::is_convertible::value and + not has_mapped_type::value + , int>::type = 0> + T get_impl(T*) const + { + if (is_array()) + { + T to_vector; + std::transform(m_value.array->begin(), m_value.array->end(), + std::inserter(to_vector, to_vector.end()), [](basic_json i) + { + return i.get(); + }); + return to_vector; + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); + } + } + + /// get an array (explicit) + template ::value and + not std::is_same::value + , int>::type = 0> + std::vector get_impl(std::vector*) const + { + if (is_array()) + { + std::vector to_vector; + to_vector.reserve(m_value.array->size()); + std::transform(m_value.array->begin(), m_value.array->end(), + std::inserter(to_vector, to_vector.end()), [](basic_json i) + { + return i.get(); + }); + return to_vector; + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); + } + } + + /// get an array (explicit) + template ::value and + not has_mapped_type::value + , int>::type = 0> + T get_impl(T*) const + { + if (is_array()) + { + return T(m_value.array->begin(), m_value.array->end()); + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); + } + } + + /// get an array (explicit) + array_t get_impl(array_t*) const + { + if (is_array()) + { + return *(m_value.array); + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); + } + } + + /// get a string (explicit) + template ::value + , int>::type = 0> + T get_impl(T*) const + { + if (is_string()) + { + return *m_value.string; + } + else + { + throw std::domain_error("type must be string, but is " + type_name()); + } + } + + /// get a number (explicit) + template::value + , int>::type = 0> + T get_impl(T*) const + { + switch (m_type) + { + case value_t::number_integer: + { + return static_cast(m_value.number_integer); + } + + case value_t::number_unsigned: + { + return static_cast(m_value.number_unsigned); + } + + case value_t::number_float: + { + return static_cast(m_value.number_float); + } + + default: + { + throw std::domain_error("type must be number, but is " + type_name()); + } + } + } + + /// get a boolean (explicit) + constexpr boolean_t get_impl(boolean_t*) const + { + return is_boolean() + ? m_value.boolean + : throw std::domain_error("type must be boolean, but is " + type_name()); + } + + /// get a pointer to the value (object) + object_t* get_impl_ptr(object_t*) noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (object) + constexpr const object_t* get_impl_ptr(const object_t*) const noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (array) + array_t* get_impl_ptr(array_t*) noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (array) + constexpr const array_t* get_impl_ptr(const array_t*) const noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (string) + string_t* get_impl_ptr(string_t*) noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (string) + constexpr const string_t* get_impl_ptr(const string_t*) const noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (boolean) + boolean_t* get_impl_ptr(boolean_t*) noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (boolean) + constexpr const boolean_t* get_impl_ptr(const boolean_t*) const noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (integer number) + number_integer_t* get_impl_ptr(number_integer_t*) noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (integer number) + constexpr const number_integer_t* get_impl_ptr(const number_integer_t*) const noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (unsigned number) + number_unsigned_t* get_impl_ptr(number_unsigned_t*) noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (unsigned number) + constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t*) const noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (floating-point number) + number_float_t* get_impl_ptr(number_float_t*) noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /// get a pointer to the value (floating-point number) + constexpr const number_float_t* get_impl_ptr(const number_float_t*) const noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /*! + @brief helper function to implement get_ref() + + This funcion helps to implement get_ref() without code duplication for + const and non-const overloads + + @tparam ThisType will be deduced as `basic_json` or `const basic_json` + + @throw std::domain_error if ReferenceType does not match underlying value + type of the current JSON + */ + template + static ReferenceType get_ref_impl(ThisType& obj) + { + // helper type + using PointerType = typename std::add_pointer::type; + + // delegate the call to get_ptr<>() + auto ptr = obj.template get_ptr(); + + if (ptr != nullptr) + { + return *ptr; + } + else + { + throw std::domain_error("incompatible ReferenceType for get_ref, actual type is " + + obj.type_name()); + } + } + + public: + + /// @name value access + /// Direct access to the stored value of a JSON value. + /// @{ + + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays + + @return copy of the JSON value, converted to type @a ValueType + + @throw std::domain_error in case passed type @a ValueType is incompatible + to JSON; example: `"type must be object, but is null"` + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,get__ValueType_const} + + @internal + The idea of using a casted null pointer to choose the correct + implementation is from . + @endinternal + + @sa @ref operator ValueType() const for implicit conversion + @sa @ref get() for pointer-member access + + @since version 1.0.0 + */ + template::value + , int>::type = 0> + ValueType get() const + { + return get_impl(static_cast(nullptr)); + } + + /*! + @brief get a pointer value (explicit) + + Explicit pointer access to the internally stored JSON value. No copies are + made. + + @warning The pointer becomes invalid if the underlying JSON object + changes. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get__PointerType} + + @sa @ref get_ptr() for explicit pointer-member access + + @since version 1.0.0 + */ + template::value + , int>::type = 0> + PointerType get() noexcept + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (explicit) + @copydoc get() + */ + template::value + , int>::type = 0> + constexpr const PointerType get() const noexcept + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (implicit) + + Implicit pointer access to the internally stored JSON value. No copies are + made. + + @warning Writing data to the pointee of the result yields an undefined + state. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. Enforced by a static + assertion. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get_ptr} + + @since version 1.0.0 + */ + template::value + , int>::type = 0> + PointerType get_ptr() noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a pointer value (implicit) + @copydoc get_ptr() + */ + template::value + and std::is_const::type>::value + , int>::type = 0> + constexpr const PointerType get_ptr() const noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() const + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a reference value (implicit) + + Implict reference access to the internally stored JSON value. No copies + are made. + + @warning Writing data to the referee of the result yields an undefined + state. + + @tparam ReferenceType reference type; must be a reference to @ref array_t, + @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or + @ref number_float_t. Enforced by static assertion. + + @return reference to the internally stored JSON value if the requested + reference type @a ReferenceType fits to the JSON value; throws + std::domain_error otherwise + + @throw std::domain_error in case passed type @a ReferenceType is + incompatible with the stored JSON value + + @complexity Constant. + + @liveexample{The example shows several calls to `get_ref()`.,get_ref} + + @since version 1.1.0 + */ + template::value + , int>::type = 0> + ReferenceType get_ref() + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a reference value (implicit) + @copydoc get_ref() + */ + template::value + and std::is_const::type>::value + , int>::type = 0> + ReferenceType get_ref() const + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a value (implicit) + + Implicit type conversion between the JSON value and a compatible value. + The call is realized by calling @ref get() const. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. + + @return copy of the JSON value, converted to type @a ValueType + + @throw std::domain_error in case passed type @a ValueType is incompatible + to JSON, thrown by @ref get() const + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,operator__ValueType} + + @since version 1.0.0 + */ + template < typename ValueType, typename + std::enable_if < + not std::is_pointer::value + and not std::is_same::value +#ifndef _MSC_VER // Fix for issue #167 operator<< abiguity under VS2015 + and not std::is_same>::value +#endif + , int >::type = 0 > + operator ValueType() const + { + // delegate the call to get<>() const + return get(); + } + + /// @} + + + //////////////////// + // element access // + //////////////////// + + /// @name element access + /// Access to the JSON value. + /// @{ + + /*! + @brief access specified array element with bounds checking + + Returns a reference to the element at specified location @a idx, with + bounds checking. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw std::domain_error if the JSON value is not an array; example: + `"cannot use at() with string"` + @throw std::out_of_range if the index @a idx is out of range of the array; + that is, `idx >= size()`; example: `"array index 7 is out of range"` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read and + written using `at()`.,at__size_type} + + @since version 1.0.0 + */ + reference at(size_type idx) + { + // at only works for arrays + if (is_array()) + { + try + { + return m_value.array->at(idx); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + } + else + { + throw std::domain_error("cannot use at() with " + type_name()); + } + } + + /*! + @brief access specified array element with bounds checking + + Returns a const reference to the element at specified location @a idx, + with bounds checking. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw std::domain_error if the JSON value is not an array; example: + `"cannot use at() with string"` + @throw std::out_of_range if the index @a idx is out of range of the array; + that is, `idx >= size()`; example: `"array index 7 is out of range"` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + `at()`.,at__size_type_const} + + @since version 1.0.0 + */ + const_reference at(size_type idx) const + { + // at only works for arrays + if (is_array()) + { + try + { + return m_value.array->at(idx); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + } + else + { + throw std::domain_error("cannot use at() with " + type_name()); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a reference to the element at with specified key @a key, with + bounds checking. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if the JSON value is not an object; example: + `"cannot use at() with boolean"` + @throw std::out_of_range if the key @a key is is not stored in the object; + that is, `find(key) == end()`; example: `"key "the fast" not found"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using `at()`.,at__object_t_key_type} + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + reference at(const typename object_t::key_type& key) + { + // at only works for objects + if (is_object()) + { + try + { + return m_value.object->at(key); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("key '" + key + "' not found"); + } + } + else + { + throw std::domain_error("cannot use at() with " + type_name()); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a const reference to the element at with specified key @a key, + with bounds checking. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw std::domain_error if the JSON value is not an object; example: + `"cannot use at() with boolean"` + @throw std::out_of_range if the key @a key is is not stored in the object; + that is, `find(key) == end()`; example: `"key "the fast" not found"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + `at()`.,at__object_t_key_type_const} + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + const_reference at(const typename object_t::key_type& key) const + { + // at only works for objects + if (is_object()) + { + try + { + return m_value.object->at(key); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("key '" + key + "' not found"); + } + } + else + { + throw std::domain_error("cannot use at() with " + type_name()); + } + } + + /*! + @brief access specified array element + + Returns a reference to the element at specified location @a idx. + + @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), + then the array is silently filled up with `null` values to make `idx` a + valid reference to the last stored element. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw std::domain_error if JSON is not an array or null; example: + `"cannot use operator[] with string"` + + @complexity Constant if @a idx is in the range of the array. Otherwise + linear in `idx - size()`. + + @liveexample{The example below shows how array elements can be read and + written using `[]` operator. Note the addition of `null` + values.,operatorarray__size_type} + + @since version 1.0.0 + */ + reference operator[](size_type idx) + { + // implicitly convert null value to an empty array + if (is_null()) + { + m_type = value_t::array; + m_value.array = create(); + assert_invariant(); + } + + // operator[] only works for arrays + if (is_array()) + { + // fill up array with null values if given idx is outside range + if (idx >= m_value.array->size()) + { + m_value.array->insert(m_value.array->end(), + idx - m_value.array->size() + 1, + basic_json()); + } + + return m_value.array->operator[](idx); + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief access specified array element + + Returns a const reference to the element at specified location @a idx. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw std::domain_error if JSON is not an array; example: `"cannot use + operator[] with null"` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + the `[]` operator.,operatorarray__size_type_const} + + @since version 1.0.0 + */ + const_reference operator[](size_type idx) const + { + // const operator[] only works for arrays + if (is_array()) + { + return m_value.array->operator[](idx); + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with string"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + reference operator[](const typename object_t::key_type& key) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + // operator[] only works for objects + if (is_object()) + { + return m_value.object->operator[](key); + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + const_reference operator[](const typename object_t::key_type& key) const + { + // const operator[] only works for objects + if (is_object()) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with string"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + template + reference operator[](T * (&key)[n]) + { + return operator[](static_cast(key)); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @note This function is required for compatibility reasons with Clang. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + template + const_reference operator[](T * (&key)[n]) const + { + return operator[](static_cast(key)); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with string"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + reference operator[](T* key) + { + // implicitly convert null to object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // at only works for objects + if (is_object()) + { + return m_value.object->operator[](key); + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + const_reference operator[](T* key) const + { + // at only works for objects + if (is_object()) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief access specified object element with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(key); + } catch(std::out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const typename object_t::key_type&), this function + does not throw if the given key @a key was not found. + + @note Unlike @ref operator[](const typename object_t::key_type& key), this + function does not implicitly add an element to the position defined by @a + key. This function is furthermore also applicable to const objects. + + @param[in] key key of the element to access + @param[in] default_value the value to return if @a key is not found + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw std::domain_error if JSON is not an object; example: `"cannot use + value() with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + + @since version 1.0.0 + */ + template ::value + , int>::type = 0> + ValueType value(const typename object_t::key_type& key, ValueType default_value) const + { + // at only works for objects + if (is_object()) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return *it; + } + else + { + return default_value; + } + } + else + { + throw std::domain_error("cannot use value() with " + type_name()); + } + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const typename object_t::key_type&, ValueType) const + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, string_t(default_value)); + } + + /*! + @brief access specified object element via JSON Pointer with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(ptr); + } catch(std::out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const json_pointer&), this function does not throw + if the given key @a key was not found. + + @param[in] ptr a JSON pointer to the element to access + @param[in] default_value the value to return if @a ptr found no value + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw std::domain_error if JSON is not an object; example: `"cannot use + value() with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value_ptr} + + @sa @ref operator[](const json_pointer&) for unchecked access by reference + + @since version 2.0.2 + */ + template ::value + , int>::type = 0> + ValueType value(const json_pointer& ptr, ValueType default_value) const + { + // at only works for objects + if (is_object()) + { + // if pointer resolves a value, return it or use default value + try + { + return ptr.get_checked(this); + } + catch (std::out_of_range&) + { + return default_value; + } + } + else + { + throw std::domain_error("cannot use value() with " + type_name()); + } + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const json_pointer&, ValueType) const + */ + string_t value(const json_pointer& ptr, const char* default_value) const + { + return value(ptr, string_t(default_value)); + } + + /*! + @brief access the first element + + Returns a reference to the first element in the container. For a JSON + container `c`, the expression `c.front()` is equivalent to `*c.begin()`. + + @return In case of a structured type (array or object), a reference to the + first element is returned. In cast of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, guarded by assertions). + @post The JSON value remains unchanged. + + @throw std::out_of_range when called on `null` value + + @liveexample{The following code shows an example for `front()`.,front} + + @sa @ref back() -- access the last element + + @since version 1.0.0 + */ + reference front() + { + return *begin(); + } + + /*! + @copydoc basic_json::front() + */ + const_reference front() const + { + return *cbegin(); + } + + /*! + @brief access the last element + + Returns a reference to the last element in the container. For a JSON + container `c`, the expression `c.back()` is equivalent to + @code {.cpp} + auto tmp = c.end(); + --tmp; + return *tmp; + @endcode + + @return In case of a structured type (array or object), a reference to the + last element is returned. In cast of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, guarded by assertions). + @post The JSON value remains unchanged. + + @throw std::out_of_range when called on `null` value. + + @liveexample{The following code shows an example for `back()`.,back} + + @sa @ref front() -- access the first element + + @since version 1.0.0 + */ + reference back() + { + auto tmp = end(); + --tmp; + return *tmp; + } + + /*! + @copydoc basic_json::back() + */ + const_reference back() const + { + auto tmp = cend(); + --tmp; + return *tmp; + } + + /*! + @brief remove element given an iterator + + Removes the element specified by iterator @a pos. The iterator @a pos must + be valid and dereferenceable. Thus the `end()` iterator (which is valid, + but is not dereferenceable) cannot be used as a value for @a pos. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] pos iterator to the element to remove + @return Iterator following the last removed element. If the iterator @a + pos refers to the last element, the `end()` iterator is returned. + + @tparam InteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw std::domain_error if called on a `null` value; example: `"cannot + use erase() with null"` + @throw std::domain_error if called on an iterator which does not belong to + the current JSON value; example: `"iterator does not fit current value"` + @throw std::out_of_range if called on a primitive type with invalid + iterator (i.e., any iterator which is not `begin()`); example: `"iterator + out of range"` + + @complexity The complexity depends on the type: + - objects: amortized constant + - arrays: linear in distance between pos and the end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType} + + @sa @ref erase(InteratorType, InteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template ::value or + std::is_same::value + , int>::type + = 0> + InteratorType erase(InteratorType pos) + { + // make sure iterator fits the current value + if (this != pos.m_object) + { + throw std::domain_error("iterator does not fit current value"); + } + + InteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (not pos.m_it.primitive_iterator.is_begin()) + { + throw std::out_of_range("iterator out of range"); + } + + if (is_string()) + { + AllocatorType alloc; + alloc.destroy(m_value.string); + alloc.deallocate(m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); + break; + } + + default: + { + throw std::domain_error("cannot use erase() with " + type_name()); + } + } + + return result; + } + + /*! + @brief remove elements given an iterator range + + Removes the element specified by the range `[first; last)`. The iterator + @a first does not need to be dereferenceable if `first == last`: erasing + an empty range is a no-op. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] first iterator to the beginning of the range to remove + @param[in] last iterator past the end of the range to remove + @return Iterator following the last removed element. If the iterator @a + second refers to the last element, the `end()` iterator is returned. + + @tparam InteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw std::domain_error if called on a `null` value; example: `"cannot + use erase() with null"` + @throw std::domain_error if called on iterators which does not belong to + the current JSON value; example: `"iterators do not fit current value"` + @throw std::out_of_range if called on a primitive type with invalid + iterators (i.e., if `first != begin()` and `last != end()`); example: + `"iterators out of range"` + + @complexity The complexity depends on the type: + - objects: `log(size()) + std::distance(first, last)` + - arrays: linear in the distance between @a first and @a last, plus linear + in the distance between @a last and end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType_IteratorType} + + @sa @ref erase(InteratorType) -- removes the element at a given position + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template ::value or + std::is_same::value + , int>::type + = 0> + InteratorType erase(InteratorType first, InteratorType last) + { + // make sure iterator fits the current value + if (this != first.m_object or this != last.m_object) + { + throw std::domain_error("iterators do not fit current value"); + } + + InteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) + { + throw std::out_of_range("iterators out of range"); + } + + if (is_string()) + { + AllocatorType alloc; + alloc.destroy(m_value.string); + alloc.deallocate(m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + { + throw std::domain_error("cannot use erase() with " + type_name()); + } + } + + return result; + } + + /*! + @brief remove element from a JSON object given a key + + Removes elements from a JSON object with the key value @a key. + + @param[in] key value of the elements to remove + + @return Number of elements removed. If @a ObjectType is the default + `std::map` type, the return value will always be `0` (@a key was not + found) or `1` (@a key was found). + + @post References and iterators to the erased elements are invalidated. + Other references and iterators are not affected. + + @throw std::domain_error when called on a type other than JSON object; + example: `"cannot use erase() with null"` + + @complexity `log(size()) + count(key)` + + @liveexample{The example shows the effect of `erase()`.,erase__key_type} + + @sa @ref erase(InteratorType) -- removes the element at a given position + @sa @ref erase(InteratorType, InteratorType) -- removes the elements in + the given range + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + size_type erase(const typename object_t::key_type& key) + { + // this erase only works for objects + if (is_object()) + { + return m_value.object->erase(key); + } + else + { + throw std::domain_error("cannot use erase() with " + type_name()); + } + } + + /*! + @brief remove element from a JSON array given an index + + Removes element from a JSON array at the index @a idx. + + @param[in] idx index of the element to remove + + @throw std::domain_error when called on a type other than JSON array; + example: `"cannot use erase() with null"` + @throw std::out_of_range when `idx >= size()`; example: `"array index 17 + is out of range"` + + @complexity Linear in distance between @a idx and the end of the container. + + @liveexample{The example shows the effect of `erase()`.,erase__size_type} + + @sa @ref erase(InteratorType) -- removes the element at a given position + @sa @ref erase(InteratorType, InteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + + @since version 1.0.0 + */ + void erase(const size_type idx) + { + // this erase only works for arrays + if (is_array()) + { + if (idx >= size()) + { + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + + m_value.array->erase(m_value.array->begin() + static_cast(idx)); + } + else + { + throw std::domain_error("cannot use erase() with " + type_name()); + } + } + + /// @} + + + //////////// + // lookup // + //////////// + + /// @name lookup + /// @{ + + /*! + @brief find an element in a JSON object + + Finds an element in a JSON object with key equivalent to @a key. If the + element is not found or the JSON value is not an object, end() is + returned. + + @param[in] key key value of the element to search for + + @return Iterator to an element with key equivalent to @a key. If no such + element is found, past-the-end (see end()) iterator is returned. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `find()` is used.,find__key_type} + + @since version 1.0.0 + */ + iterator find(typename object_t::key_type key) + { + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(key); + } + + return result; + } + + /*! + @brief find an element in a JSON object + @copydoc find(typename object_t::key_type) + */ + const_iterator find(typename object_t::key_type key) const + { + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(key); + } + + return result; + } + + /*! + @brief returns the number of occurrences of a key in a JSON object + + Returns the number of elements with key @a key. If ObjectType is the + default `std::map` type, the return value will always be `0` (@a key was + not found) or `1` (@a key was found). + + @param[in] key key value of the element to count + + @return Number of elements with key @a key. If the JSON value is not an + object, the return value will be `0`. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `count()` is used.,count} + + @since version 1.0.0 + */ + size_type count(typename object_t::key_type key) const + { + // return 0 for all nonobject types + return is_object() ? m_value.object->count(key) : 0; + } + + /// @} + + + /////////////// + // iterators // + /////////////// + + /// @name iterators + /// @{ + + /*! + @brief returns an iterator to the first element + + Returns an iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `begin()`.,begin} + + @sa @ref cbegin() -- returns a const iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + iterator begin() noexcept + { + iterator result(this); + result.set_begin(); + return result; + } + + /*! + @copydoc basic_json::cbegin() + */ + const_iterator begin() const noexcept + { + return cbegin(); + } + + /*! + @brief returns a const iterator to the first element + + Returns a const iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).begin()`. + + @liveexample{The following code shows an example for `cbegin()`.,cbegin} + + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + const_iterator cbegin() const noexcept + { + const_iterator result(this); + result.set_begin(); + return result; + } + + /*! + @brief returns an iterator to one past the last element + + Returns an iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `end()`.,end} + + @sa @ref cend() -- returns a const iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + iterator end() noexcept + { + iterator result(this); + result.set_end(); + return result; + } + + /*! + @copydoc basic_json::cend() + */ + const_iterator end() const noexcept + { + return cend(); + } + + /*! + @brief returns a const iterator to one past the last element + + Returns a const iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).end()`. + + @liveexample{The following code shows an example for `cend()`.,cend} + + @sa @ref end() -- returns an iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + const_iterator cend() const noexcept + { + const_iterator result(this); + result.set_end(); + return result; + } + + /*! + @brief returns an iterator to the reverse-beginning + + Returns an iterator to the reverse-beginning; that is, the last element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(end())`. + + @liveexample{The following code shows an example for `rbegin()`.,rbegin} + + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + /*! + @copydoc basic_json::crbegin() + */ + const_reverse_iterator rbegin() const noexcept + { + return crbegin(); + } + + /*! + @brief returns an iterator to the reverse-end + + Returns an iterator to the reverse-end; that is, one before the first + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(begin())`. + + @liveexample{The following code shows an example for `rend()`.,rend} + + @sa @ref crend() -- returns a const reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + /*! + @copydoc basic_json::crend() + */ + const_reverse_iterator rend() const noexcept + { + return crend(); + } + + /*! + @brief returns a const reverse iterator to the last element + + Returns a const iterator to the reverse-beginning; that is, the last + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rbegin()`. + + @liveexample{The following code shows an example for `crbegin()`.,crbegin} + + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(cend()); + } + + /*! + @brief returns a const reverse iterator to one before the first + + Returns a const reverse iterator to the reverse-end; that is, one before + the first element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rend()`. + + @liveexample{The following code shows an example for `crend()`.,crend} + + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(cbegin()); + } + + private: + // forward declaration + template class iteration_proxy; + + public: + /*! + @brief wrapper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + @note The name of this function is not yet final and may change in the + future. + */ + static iteration_proxy iterator_wrapper(reference cont) + { + return iteration_proxy(cont); + } + + /*! + @copydoc iterator_wrapper(reference) + */ + static iteration_proxy iterator_wrapper(const_reference cont) + { + return iteration_proxy(cont); + } + + /// @} + + + ////////////// + // capacity // + ////////////// + + /// @name capacity + /// @{ + + /*! + @brief checks whether the container is empty + + Checks if a JSON value has no elements. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `true` + boolean | `false` + string | `false` + number | `false` + object | result of function `object_t::empty()` + array | result of function `array_t::empty()` + + @note This function does not return whether a string stored as JSON value + is empty - it returns whether the JSON container itself is empty which is + false in the case of a string. + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `empty()` functions have constant + complexity. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `begin() == end()`. + + @liveexample{The following code uses `empty()` to check if a JSON + object contains any elements.,empty} + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + bool empty() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return true; + } + + case value_t::array: + { + // delegate call to array_t::empty() + return m_value.array->empty(); + } + + case value_t::object: + { + // delegate call to object_t::empty() + return m_value.object->empty(); + } + + default: + { + // all other types are nonempty + return false; + } + } + } + + /*! + @brief returns the number of elements + + Returns the number of elements in a JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` + boolean | `1` + string | `1` + number | `1` + object | result of function object_t::size() + array | result of function array_t::size() + + @note This function does not return the length of a string stored as JSON + value - it returns the number of elements in the JSON value which is 1 in + the case of a string. + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their size() functions have constant + complexity. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `std::distance(begin(), end())`. + + @liveexample{The following code calls `size()` on the different value + types.,size} + + @sa @ref empty() -- checks whether the container is empty + @sa @ref max_size() -- returns the maximal number of elements + + @since version 1.0.0 + */ + size_type size() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return 0; + } + + case value_t::array: + { + // delegate call to array_t::size() + return m_value.array->size(); + } + + case value_t::object: + { + // delegate call to object_t::size() + return m_value.object->size(); + } + + default: + { + // all other types have size 1 + return 1; + } + } + } + + /*! + @brief returns the maximum possible number of elements + + Returns the maximum number of elements a JSON value is able to hold due to + system or library implementation limitations, i.e. `std::distance(begin(), + end())` for the JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` (same as `size()`) + boolean | `1` (same as `size()`) + string | `1` (same as `size()`) + number | `1` (same as `size()`) + object | result of function `object_t::max_size()` + array | result of function `array_t::max_size()` + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `max_size()` functions have constant + complexity. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of returning `b.size()` where `b` is the largest + possible JSON value. + + @liveexample{The following code calls `max_size()` on the different value + types. Note the output is implementation specific.,max_size} + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + size_type max_size() const noexcept + { + switch (m_type) + { + case value_t::array: + { + // delegate call to array_t::max_size() + return m_value.array->max_size(); + } + + case value_t::object: + { + // delegate call to object_t::max_size() + return m_value.object->max_size(); + } + + default: + { + // all other types have max_size() == size() + return size(); + } + } + } + + /// @} + + + /////////////// + // modifiers // + /////////////// + + /// @name modifiers + /// @{ + + /*! + @brief clears the contents + + Clears the content of a JSON value and resets it to the default value as + if @ref basic_json(value_t) would have been called: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @note Floating-point numbers are set to `0.0` which will be serialized to + `0`. The vale type remains @ref number_float_t. + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows the effect of `clear()` to different + JSON types.,clear} + + @since version 1.0.0 + */ + void clear() noexcept + { + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = 0; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = 0; + break; + } + + case value_t::number_float: + { + m_value.number_float = 0.0; + break; + } + + case value_t::boolean: + { + m_value.boolean = false; + break; + } + + case value_t::string: + { + m_value.string->clear(); + break; + } + + case value_t::array: + { + m_value.array->clear(); + break; + } + + case value_t::object: + { + m_value.object->clear(); + break; + } + + default: + { + break; + } + } + } + + /*! + @brief add an object to an array + + Appends the given element @a val to the end of the JSON value. If the + function is called on a JSON null value, an empty array is created before + appending @a val. + + @param[in] val the value to add to the JSON array + + @throw std::domain_error when called on a type other than JSON array or + null; example: `"cannot use push_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON array. Note how the `null` value was silently + converted to a JSON array.,push_back} + + @since version 1.0.0 + */ + void push_back(basic_json&& val) + { + // push_back only works for null objects or arrays + if (not(is_null() or is_array())) + { + throw std::domain_error("cannot use push_back() with " + type_name()); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (move semantics) + m_value.array->push_back(std::move(val)); + // invalidate object + val.m_type = value_t::null; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(basic_json&& val) + { + push_back(std::move(val)); + return *this; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + void push_back(const basic_json& val) + { + // push_back only works for null objects or arrays + if (not(is_null() or is_array())) + { + throw std::domain_error("cannot use push_back() with " + type_name()); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array + m_value.array->push_back(val); + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(const basic_json& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + Inserts the given element @a val to the JSON object. If the function is + called on a JSON null value, an empty object is created before inserting + @a val. + + @param[in] val the value to add to the JSON object + + @throw std::domain_error when called on a type other than JSON object or + null; example: `"cannot use push_back() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON object. Note how the `null` value was silently + converted to a JSON object.,push_back__object_t__value} + + @since version 1.0.0 + */ + void push_back(const typename object_t::value_type& val) + { + // push_back only works for null objects or objects + if (not(is_null() or is_object())) + { + throw std::domain_error("cannot use push_back() with " + type_name()); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array + m_value.object->insert(val); + } + + /*! + @brief add an object to an object + @copydoc push_back(const typename object_t::value_type&) + */ + reference operator+=(const typename object_t::value_type& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + This function allows to use `push_back` with an initializer list. In case + + 1. the current value is an object, + 2. the initializer list @a init contains only two elements, and + 3. the first element of @a init is a string, + + @a init is converted into an object element and added using + @ref push_back(const typename object_t::value_type&). Otherwise, @a init + is converted to a JSON value and added using @ref push_back(basic_json&&). + + @param init an initializer list + + @complexity Linear in the size of the initializer list @a init. + + @note This function is required to resolve an ambiguous overload error, + because pairs like `{"key", "value"}` can be both interpreted as + `object_t::value_type` or `std::initializer_list`, see + https://github.com/nlohmann/json/issues/235 for more information. + + @liveexample{The example shows how initializer lists are treated as + objects when possible.,push_back__initializer_list} + */ + void push_back(std::initializer_list init) + { + if (is_object() and init.size() == 2 and init.begin()->is_string()) + { + const string_t key = *init.begin(); + push_back(typename object_t::value_type(key, *(init.begin() + 1))); + } + else + { + push_back(basic_json(init)); + } + } + + /*! + @brief add an object to an object + @copydoc push_back(std::initializer_list) + */ + reference operator+=(std::initializer_list init) + { + push_back(init); + return *this; + } + + /*! + @brief inserts element + + Inserts element @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] val element to insert + @return iterator pointing to the inserted @a val. + + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + + @complexity Constant plus linear in the distance between pos and end of the + container. + + @liveexample{The example shows how `insert()` is used.,insert} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const basic_json& val) + { + // insert only works for arrays + if (is_array()) + { + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); + return result; + } + else + { + throw std::domain_error("cannot use insert() with " + type_name()); + } + } + + /*! + @brief inserts element + @copydoc insert(const_iterator, const basic_json&) + */ + iterator insert(const_iterator pos, basic_json&& val) + { + return insert(pos, val); + } + + /*! + @brief inserts elements + + Inserts @a cnt copies of @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] cnt number of copies of @a val to insert + @param[in] val element to insert + @return iterator pointing to the first element inserted, or @a pos if + `cnt==0` + + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + + @complexity Linear in @a cnt plus linear in the distance between @a pos + and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__count} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + { + // insert only works for arrays + if (is_array()) + { + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + return result; + } + else + { + throw std::domain_error("cannot use insert() with " + type_name()); + } + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)` before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + @throw std::domain_error if @a first and @a last do not belong to the same + JSON value; example: `"iterators do not fit"` + @throw std::domain_error if @a first or @a last are iterators into + container for which insert is called; example: `"passed iterators may not + belong to container"` + + @return iterator pointing to the first element inserted, or @a pos if + `first==last` + + @complexity Linear in `std::distance(first, last)` plus linear in the + distance between @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__range} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const_iterator first, const_iterator last) + { + // insert only works for arrays + if (not is_array()) + { + throw std::domain_error("cannot use insert() with " + type_name()); + } + + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // check if range iterators belong to the same JSON object + if (first.m_object != last.m_object) + { + throw std::domain_error("iterators do not fit"); + } + + if (first.m_object == this or last.m_object == this) + { + throw std::domain_error("passed iterators may not belong to container"); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert( + pos.m_it.array_iterator, + first.m_it.array_iterator, + last.m_it.array_iterator); + return result; + } + + /*! + @brief inserts elements + + Inserts elements from initializer list @a ilist before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] ilist initializer list to insert the values from + + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + + @return iterator pointing to the first element inserted, or @a pos if + `ilist` is empty + + @complexity Linear in `ilist.size()` plus linear in the distance between + @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__ilist} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, std::initializer_list ilist) + { + // insert only works for arrays + if (not is_array()) + { + throw std::domain_error("cannot use insert() with " + type_name()); + } + + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist); + return result; + } + + /*! + @brief exchanges the values + + Exchanges the contents of the JSON value with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be swapped with + `swap()`.,swap__reference} + + @since version 1.0.0 + */ + void swap(reference other) noexcept ( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + std::swap(m_type, other.m_type); + std::swap(m_value, other.m_value); + assert_invariant(); + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON array with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other array to exchange the contents with + + @throw std::domain_error when JSON value is not an array; example: `"cannot + use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how arrays can be swapped with + `swap()`.,swap__array_t} + + @since version 1.0.0 + */ + void swap(array_t& other) + { + // swap only works for arrays + if (is_array()) + { + std::swap(*(m_value.array), other); + } + else + { + throw std::domain_error("cannot use swap() with " + type_name()); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON object with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other object to exchange the contents with + + @throw std::domain_error when JSON value is not an object; example: + `"cannot use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how objects can be swapped with + `swap()`.,swap__object_t} + + @since version 1.0.0 + */ + void swap(object_t& other) + { + // swap only works for objects + if (is_object()) + { + std::swap(*(m_value.object), other); + } + else + { + throw std::domain_error("cannot use swap() with " + type_name()); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON string with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other string to exchange the contents with + + @throw std::domain_error when JSON value is not a string; example: `"cannot + use swap() with boolean"` + + @complexity Constant. + + @liveexample{The example below shows how strings can be swapped with + `swap()`.,swap__string_t} + + @since version 1.0.0 + */ + void swap(string_t& other) + { + // swap only works for strings + if (is_string()) + { + std::swap(*(m_value.string), other); + } + else + { + throw std::domain_error("cannot use swap() with " + type_name()); + } + } + + /// @} + + + ////////////////////////////////////////// + // lexicographical comparison operators // + ////////////////////////////////////////// + + /// @name lexicographical comparison operators + /// @{ + + private: + /*! + @brief comparison operator for JSON types + + Returns an ordering that is similar to Python: + - order: null < boolean < number < object < array < string + - furthermore, each type is not smaller than itself + + @since version 1.0.0 + */ + friend bool operator<(const value_t lhs, const value_t rhs) noexcept + { + static constexpr std::array order = {{ + 0, // null + 3, // object + 4, // array + 5, // string + 1, // boolean + 2, // integer + 2, // unsigned + 2, // float + } + }; + + // discarded values are not comparable + if (lhs == value_t::discarded or rhs == value_t::discarded) + { + return false; + } + + return order[static_cast(lhs)] < order[static_cast(rhs)]; + } + + public: + /*! + @brief comparison: equal + + Compares two JSON values for equality according to the following rules: + - Two JSON values are equal if (1) they are from the same type and (2) + their stored values are the same. + - Integer and floating-point numbers are automatically converted before + comparison. Floating-point numbers are compared indirectly: two + floating-point numbers `f1` and `f2` are considered equal if neither + `f1 > f2` nor `f2 > f1` holds. + - Two JSON null values are equal. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are equal + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__equal} + + @since version 1.0.0 + */ + friend bool operator==(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + { + return *lhs.m_value.array == *rhs.m_value.array; + } + case value_t::object: + { + return *lhs.m_value.object == *rhs.m_value.object; + } + case value_t::null: + { + return true; + } + case value_t::string: + { + return *lhs.m_value.string == *rhs.m_value.string; + } + case value_t::boolean: + { + return lhs.m_value.boolean == rhs.m_value.boolean; + } + case value_t::number_integer: + { + return lhs.m_value.number_integer == rhs.m_value.number_integer; + } + case value_t::number_unsigned: + { + return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; + } + case value_t::number_float: + { + return lhs.m_value.number_float == rhs.m_value.number_float; + } + default: + { + return false; + } + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); + } + + return false; + } + + /*! + @brief comparison: equal + + The functions compares the given JSON value against a null pointer. As the + null pointer can be used to initialize a JSON value to null, a comparison + of JSON value @a v with a null pointer should be equivalent to call + `v.is_null()`. + + @param[in] v JSON value to consider + @return whether @a v is null + + @complexity Constant. + + @liveexample{The example compares several JSON types to the null pointer. + ,operator__equal__nullptr_t} + + @since version 1.0.0 + */ + friend bool operator==(const_reference v, std::nullptr_t) noexcept + { + return v.is_null(); + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, std::nullptr_t) + */ + friend bool operator==(std::nullptr_t, const_reference v) noexcept + { + return v.is_null(); + } + + /*! + @brief comparison: not equal + + Compares two JSON values for inequality by calculating `not (lhs == rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are not equal + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__notequal} + + @since version 1.0.0 + */ + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs == rhs); + } + + /*! + @brief comparison: not equal + + The functions compares the given JSON value against a null pointer. As the + null pointer can be used to initialize a JSON value to null, a comparison + of JSON value @a v with a null pointer should be equivalent to call + `not v.is_null()`. + + @param[in] v JSON value to consider + @return whether @a v is not null + + @complexity Constant. + + @liveexample{The example compares several JSON types to the null pointer. + ,operator__notequal__nullptr_t} + + @since version 1.0.0 + */ + friend bool operator!=(const_reference v, std::nullptr_t) noexcept + { + return not v.is_null(); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, std::nullptr_t) + */ + friend bool operator!=(std::nullptr_t, const_reference v) noexcept + { + return not v.is_null(); + } + + /*! + @brief comparison: less than + + Compares whether one JSON value @a lhs is less than another JSON value @a + rhs according to the following rules: + - If @a lhs and @a rhs have the same type, the values are compared using + the default `<` operator. + - Integer and floating-point numbers are automatically converted before + comparison + - In case @a lhs and @a rhs have different types, the values are ignored + and the order of the types is considered, see + @ref operator<(const value_t, const value_t). + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than @a rhs + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__less} + + @since version 1.0.0 + */ + friend bool operator<(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + { + return *lhs.m_value.array < *rhs.m_value.array; + } + case value_t::object: + { + return *lhs.m_value.object < *rhs.m_value.object; + } + case value_t::null: + { + return false; + } + case value_t::string: + { + return *lhs.m_value.string < *rhs.m_value.string; + } + case value_t::boolean: + { + return lhs.m_value.boolean < rhs.m_value.boolean; + } + case value_t::number_integer: + { + return lhs.m_value.number_integer < rhs.m_value.number_integer; + } + case value_t::number_unsigned: + { + return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; + } + case value_t::number_float: + { + return lhs.m_value.number_float < rhs.m_value.number_float; + } + default: + { + return false; + } + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; + } + + // We only reach this line if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + return operator<(lhs_type, rhs_type); + } + + /*! + @brief comparison: less than or equal + + Compares whether one JSON value @a lhs is less than or equal to another + JSON value by calculating `not (rhs < lhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than or equal to @a rhs + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greater} + + @since version 1.0.0 + */ + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept + { + return not (rhs < lhs); + } + + /*! + @brief comparison: greater than + + Compares whether one JSON value @a lhs is greater than another + JSON value by calculating `not (lhs <= rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than to @a rhs + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__lessequal} + + @since version 1.0.0 + */ + friend bool operator>(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs <= rhs); + } + + /*! + @brief comparison: greater than or equal + + Compares whether one JSON value @a lhs is greater than or equal to another + JSON value by calculating `not (lhs < rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than or equal to @a rhs + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greaterequal} + + @since version 1.0.0 + */ + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs < rhs); + } + + /// @} + + + /////////////////// + // serialization // + /////////////////// + + /// @name serialization + /// @{ + + /*! + @brief serialize to stream + + Serialize the given JSON value @a j to the output stream @a o. The JSON + value will be serialized using the @ref dump member function. The + indentation of the output can be controlled with the member variable + `width` of the output stream @a o. For instance, using the manipulator + `std::setw(4)` on @a o sets the indentation level to `4` and the + serialization result is the same as calling `dump(4)`. + + @note During serializaion, the locale and the precision of the output + stream @a o are changed. The original values are restored when the + function returns. + + @param[in,out] o stream to serialize to + @param[in] j JSON value to serialize + + @return the stream @a o + + @complexity Linear. + + @liveexample{The example below shows the serialization with different + parameters to `width` to adjust the indentation level.,operator_serialize} + + @since version 1.0.0 + */ + friend std::ostream& operator<<(std::ostream& o, const basic_json& j) + { + // read width member and use it as indentation parameter if nonzero + const bool pretty_print = (o.width() > 0); + const auto indentation = (pretty_print ? o.width() : 0); + + // reset width to 0 for subsequent calls to this stream + o.width(0); + + // fix locale problems + const auto old_locale = o.imbue(std::locale(std::locale(), new DecimalSeparator)); + // set precision + + // 6, 15 or 16 digits of precision allows round-trip IEEE 754 + // string->float->string, string->double->string or string->long + // double->string; to be safe, we read this value from + // std::numeric_limits::digits10 + const auto old_precision = o.precision(std::numeric_limits::digits10); + + // do the actual serialization + j.dump(o, pretty_print, static_cast(indentation)); + + // reset locale and precision + o.imbue(old_locale); + o.precision(old_precision); + return o; + } + + /*! + @brief serialize to stream + @copydoc operator<<(std::ostream&, const basic_json&) + */ + friend std::ostream& operator>>(const basic_json& j, std::ostream& o) + { + return o << j; + } + + /// @} + + + ///////////////////// + // deserialization // + ///////////////////// + + /// @name deserialization + /// @{ + + /*! + @brief deserialize from string + + @param[in] s string to read a serialized JSON value from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__string__parser_callback_t} + + @sa @ref parse(std::istream&, const parser_callback_t) for a version that + reads from an input stream + + @since version 1.0.0 + */ + static basic_json parse(const string_t& s, + const parser_callback_t cb = nullptr) + { + return parser(s, cb).parse(); + } + + /*! + @brief deserialize from stream + + @param[in,out] i stream to read a serialized JSON value from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__istream__parser_callback_t} + + @sa @ref parse(const string_t&, const parser_callback_t) for a version + that reads from a string + + @since version 1.0.0 + */ + static basic_json parse(std::istream& i, + const parser_callback_t cb = nullptr) + { + return parser(i, cb).parse(); + } + + /*! + @copydoc parse(std::istream&, const parser_callback_t) + */ + static basic_json parse(std::istream&& i, + const parser_callback_t cb = nullptr) + { + return parser(i, cb).parse(); + } + + /*! + @brief deserialize from stream + + Deserializes an input stream to a JSON value. + + @param[in,out] i input stream to read a serialized JSON value from + @param[in,out] j JSON value to write the deserialized input to + + @throw std::invalid_argument in case of parse errors + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below shows how a JSON value is constructed by + reading a serialization from a stream.,operator_deserialize} + + @sa parse(std::istream&, const parser_callback_t) for a variant with a + parser callback function to filter values while parsing + + @since version 1.0.0 + */ + friend std::istream& operator<<(basic_json& j, std::istream& i) + { + j = parser(i).parse(); + return i; + } + + /*! + @brief deserialize from stream + @copydoc operator<<(basic_json&, std::istream&) + */ + friend std::istream& operator>>(std::istream& i, basic_json& j) + { + j = parser(i).parse(); + return i; + } + + /// @} + + + private: + /////////////////////////// + // convenience functions // + /////////////////////////// + + /*! + @brief return the type as string + + Returns the type name as string to be used in error messages - usually to + indicate that a function was called on a wrong JSON type. + + @return basically a string representation of a the @ref m_type member + + @complexity Constant. + + @since version 1.0.0 + */ + std::string type_name() const + { + switch (m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } + } + + /*! + @brief calculates the extra space to escape a JSON string + + @param[in] s the string to escape + @return the number of characters required to escape string @a s + + @complexity Linear in the length of string @a s. + */ + static std::size_t extra_space(const string_t& s) noexcept + { + return std::accumulate(s.begin(), s.end(), size_t{}, + [](size_t res, typename string_t::value_type c) + { + switch (c) + { + case '"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + { + // from c (1 byte) to \x (2 bytes) + return res + 1; + } + + default: + { + if (c >= 0x00 and c <= 0x1f) + { + // from c (1 byte) to \uxxxx (6 bytes) + return res + 5; + } + else + { + return res; + } + } + } + }); + } + + /*! + @brief escape a string + + Escape a string by replacing certain special characters by a sequence of + an escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. + + @param[in] s the string to escape + @return the escaped string + + @complexity Linear in the length of string @a s. + */ + static string_t escape_string(const string_t& s) + { + const auto space = extra_space(s); + if (space == 0) + { + return s; + } + + // create a result string of necessary size + string_t result(s.size() + space, '\\'); + std::size_t pos = 0; + + for (const auto& c : s) + { + switch (c) + { + // quotation mark (0x22) + case '"': + { + result[pos + 1] = '"'; + pos += 2; + break; + } + + // reverse solidus (0x5c) + case '\\': + { + // nothing to change + pos += 2; + break; + } + + // backspace (0x08) + case '\b': + { + result[pos + 1] = 'b'; + pos += 2; + break; + } + + // formfeed (0x0c) + case '\f': + { + result[pos + 1] = 'f'; + pos += 2; + break; + } + + // newline (0x0a) + case '\n': + { + result[pos + 1] = 'n'; + pos += 2; + break; + } + + // carriage return (0x0d) + case '\r': + { + result[pos + 1] = 'r'; + pos += 2; + break; + } + + // horizontal tab (0x09) + case '\t': + { + result[pos + 1] = 't'; + pos += 2; + break; + } + + default: + { + if (c >= 0x00 and c <= 0x1f) + { + // convert a number 0..15 to its hex representation + // (0..f) + static const char hexify[16] = + { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + // print character c as \uxxxx + for (const char m : + { 'u', '0', '0', hexify[c >> 4], hexify[c & 0x0f] + }) + { + result[++pos] = m; + } + + ++pos; + } + else + { + // all other characters are added as-is + result[pos++] = c; + } + break; + } + } + } + + return result; + } + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. Note that + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + + @param[out] o stream to write to + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + void dump(std::ostream& o, + const bool pretty_print, + const unsigned int indent_step, + const unsigned int current_indent = 0) const + { + // variable to hold indentation for recursive calls + unsigned int new_indent = current_indent; + + switch (m_type) + { + case value_t::object: + { + if (m_value.object->empty()) + { + o << "{}"; + return; + } + + o << "{"; + + // increase indentation + if (pretty_print) + { + new_indent += indent_step; + o << "\n"; + } + + for (auto i = m_value.object->cbegin(); i != m_value.object->cend(); ++i) + { + if (i != m_value.object->cbegin()) + { + o << (pretty_print ? ",\n" : ","); + } + o << string_t(new_indent, ' ') << "\"" + << escape_string(i->first) << "\":" + << (pretty_print ? " " : ""); + i->second.dump(o, pretty_print, indent_step, new_indent); + } + + // decrease indentation + if (pretty_print) + { + new_indent -= indent_step; + o << "\n"; + } + + o << string_t(new_indent, ' ') + "}"; + return; + } + + case value_t::array: + { + if (m_value.array->empty()) + { + o << "[]"; + return; + } + + o << "["; + + // increase indentation + if (pretty_print) + { + new_indent += indent_step; + o << "\n"; + } + + for (auto i = m_value.array->cbegin(); i != m_value.array->cend(); ++i) + { + if (i != m_value.array->cbegin()) + { + o << (pretty_print ? ",\n" : ","); + } + o << string_t(new_indent, ' '); + i->dump(o, pretty_print, indent_step, new_indent); + } + + // decrease indentation + if (pretty_print) + { + new_indent -= indent_step; + o << "\n"; + } + + o << string_t(new_indent, ' ') << "]"; + return; + } + + case value_t::string: + { + o << string_t("\"") << escape_string(*m_value.string) << "\""; + return; + } + + case value_t::boolean: + { + o << (m_value.boolean ? "true" : "false"); + return; + } + + case value_t::number_integer: + { + o << m_value.number_integer; + return; + } + + case value_t::number_unsigned: + { + o << m_value.number_unsigned; + return; + } + + case value_t::number_float: + { + if (m_value.number_float == 0) + { + // special case for zero to get "0.0"/"-0.0" + o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0"); + } + else + { + o << m_value.number_float; + } + return; + } + + case value_t::discarded: + { + o << ""; + return; + } + + case value_t::null: + { + o << "null"; + return; + } + } + } + + private: + ////////////////////// + // member variables // + ////////////////////// + + /// the type of the current element + value_t m_type = value_t::null; + + /// the value of the current element + json_value m_value = {}; + + + private: + /////////////// + // iterators // + /////////////// + + /*! + @brief an iterator for primitive JSON types + + This class models an iterator for primitive JSON types (boolean, number, + string). It's only purpose is to allow the iterator/const_iterator classes + to "iterate" over primitive values. Internally, the iterator is modeled by + a `difference_type` variable. Value begin_value (`0`) models the begin, + end_value (`1`) models past the end. + */ + class primitive_iterator_t + { + public: + /// set iterator to a defined beginning + void set_begin() noexcept + { + m_it = begin_value; + } + + /// set iterator to a defined past the end + void set_end() noexcept + { + m_it = end_value; + } + + /// return whether the iterator can be dereferenced + constexpr bool is_begin() const noexcept + { + return (m_it == begin_value); + } + + /// return whether the iterator is at end + constexpr bool is_end() const noexcept + { + return (m_it == end_value); + } + + /// return reference to the value to change and compare + operator difference_type& () noexcept + { + return m_it; + } + + /// return value to compare + constexpr operator difference_type () const noexcept + { + return m_it; + } + + private: + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value + 1; + + /// iterator as signed integer type + difference_type m_it = std::numeric_limits::denorm_min(); + }; + + /*! + @brief an iterator value + + @note This structure could easily be a union, but MSVC currently does not + allow unions members with complex constructors, see + https://github.com/nlohmann/json/pull/105. + */ + struct internal_iterator + { + /// iterator for JSON objects + typename object_t::iterator object_iterator; + /// iterator for JSON arrays + typename array_t::iterator array_iterator; + /// generic iterator for all other types + primitive_iterator_t primitive_iterator; + + /// create an uninitialized internal_iterator + internal_iterator() noexcept + : object_iterator(), array_iterator(), primitive_iterator() + {} + }; + + /// proxy class for the iterator_wrapper functions + template + class iteration_proxy + { + private: + /// helper class for iteration + class iteration_proxy_internal + { + private: + /// the iterator + IteratorType anchor; + /// an index for arrays (used to create key names) + size_t array_index = 0; + + public: + explicit iteration_proxy_internal(IteratorType it) noexcept + : anchor(it) + {} + + /// dereference operator (needed for range-based for) + iteration_proxy_internal& operator*() + { + return *this; + } + + /// increment operator (needed for range-based for) + iteration_proxy_internal& operator++() + { + ++anchor; + ++array_index; + + return *this; + } + + /// inequality operator (needed for range-based for) + bool operator!= (const iteration_proxy_internal& o) const + { + return anchor != o.anchor; + } + + /// return key of the iterator + typename basic_json::string_t key() const + { + assert(anchor.m_object != nullptr); + + switch (anchor.m_object->type()) + { + // use integer array index as key + case value_t::array: + { + return std::to_string(array_index); + } + + // use key from the object + case value_t::object: + { + return anchor.key(); + } + + // use an empty key for all primitive types + default: + { + return ""; + } + } + } + + /// return value of the iterator + typename IteratorType::reference value() const + { + return anchor.value(); + } + }; + + /// the container to iterate + typename IteratorType::reference container; + + public: + /// construct iteration proxy from a container + explicit iteration_proxy(typename IteratorType::reference cont) + : container(cont) + {} + + /// return iterator begin (needed for range-based for) + iteration_proxy_internal begin() noexcept + { + return iteration_proxy_internal(container.begin()); + } + + /// return iterator end (needed for range-based for) + iteration_proxy_internal end() noexcept + { + return iteration_proxy_internal(container.end()); + } + }; + + public: + /*! + @brief a const random access iterator for the @ref basic_json class + + This class implements a const iterator for the @ref basic_json class. From + this class, the @ref iterator class is derived. + + @note An iterator is called *initialized* when a pointer to a JSON value + has been set (e.g., by a constructor or a copy assignment). If the + iterator is default-constructed, it is *uninitialized* and most + methods are undefined. The library uses assertions to detect calls + on uninitialized iterators. + + @requirement The class satisfies the following concept requirements: + - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): + The iterator that can be moved to point (forward and backward) to any + element in constant time. + + @since version 1.0.0 + */ + class const_iterator : public std::iterator + { + /// allow basic_json to access private members + friend class basic_json; + + public: + /// the type of the values when the iterator is dereferenced + using value_type = typename basic_json::value_type; + /// a type to represent differences between iterators + using difference_type = typename basic_json::difference_type; + /// defines a pointer to the type iterated over (value_type) + using pointer = typename basic_json::const_pointer; + /// defines a reference to the type iterated over (value_type) + using reference = typename basic_json::const_reference; + /// the category of the iterator + using iterator_category = std::bidirectional_iterator_tag; + + /// default constructor + const_iterator() = default; + + /*! + @brief constructor for a given JSON instance + @param[in] object pointer to a JSON object for this iterator + @pre object != nullptr + @post The iterator is initialized; i.e. `m_object != nullptr`. + */ + explicit const_iterator(pointer object) noexcept + : m_object(object) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + m_it.object_iterator = typename object_t::iterator(); + break; + } + + case basic_json::value_t::array: + { + m_it.array_iterator = typename array_t::iterator(); + break; + } + + default: + { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } + } + } + + /*! + @brief copy constructor given a non-const iterator + @param[in] other iterator to copy from + @note It is not checked whether @a other is initialized. + */ + explicit const_iterator(const iterator& other) noexcept + : m_object(other.m_object) + { + if (m_object != nullptr) + { + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + m_it.object_iterator = other.m_it.object_iterator; + break; + } + + case basic_json::value_t::array: + { + m_it.array_iterator = other.m_it.array_iterator; + break; + } + + default: + { + m_it.primitive_iterator = other.m_it.primitive_iterator; + break; + } + } + } + } + + /*! + @brief copy constructor + @param[in] other iterator to copy from + @note It is not checked whether @a other is initialized. + */ + const_iterator(const const_iterator& other) noexcept + : m_object(other.m_object), m_it(other.m_it) + {} + + /*! + @brief copy assignment + @param[in,out] other iterator to copy from + @note It is not checked whether @a other is initialized. + */ + const_iterator& operator=(const_iterator other) noexcept( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + std::swap(m_object, other.m_object); + std::swap(m_it, other.m_it); + return *this; + } + + private: + /*! + @brief set the iterator to the first value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_begin() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + m_it.object_iterator = m_object->m_value.object->begin(); + break; + } + + case basic_json::value_t::array: + { + m_it.array_iterator = m_object->m_value.array->begin(); + break; + } + + case basic_json::value_t::null: + { + // set to end so begin()==end() is true: null is empty + m_it.primitive_iterator.set_end(); + break; + } + + default: + { + m_it.primitive_iterator.set_begin(); + break; + } + } + } + + /*! + @brief set the iterator past the last value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_end() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + m_it.object_iterator = m_object->m_value.object->end(); + break; + } + + case basic_json::value_t::array: + { + m_it.array_iterator = m_object->m_value.array->end(); + break; + } + + default: + { + m_it.primitive_iterator.set_end(); + break; + } + } + } + + public: + /*! + @brief return a reference to the value pointed to by the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator*() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return m_it.object_iterator->second; + } + + case basic_json::value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return *m_it.array_iterator; + } + + case basic_json::value_t::null: + { + throw std::out_of_range("cannot get value"); + } + + default: + { + if (m_it.primitive_iterator.is_begin()) + { + return *m_object; + } + else + { + throw std::out_of_range("cannot get value"); + } + } + } + } + + /*! + @brief dereference the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + pointer operator->() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case basic_json::value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return &*m_it.array_iterator; + } + + default: + { + if (m_it.primitive_iterator.is_begin()) + { + return m_object; + } + else + { + throw std::out_of_range("cannot get value"); + } + } + } + } + + /*! + @brief post-increment (it++) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const_iterator operator++(int) + { + auto result = *this; + ++(*this); + return result; + } + + /*! + @brief pre-increment (++it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const_iterator& operator++() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + std::advance(m_it.object_iterator, 1); + break; + } + + case basic_json::value_t::array: + { + std::advance(m_it.array_iterator, 1); + break; + } + + default: + { + ++m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief post-decrement (it--) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const_iterator operator--(int) + { + auto result = *this; + --(*this); + return result; + } + + /*! + @brief pre-decrement (--it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const_iterator& operator--() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + std::advance(m_it.object_iterator, -1); + break; + } + + case basic_json::value_t::array: + { + std::advance(m_it.array_iterator, -1); + break; + } + + default: + { + --m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief comparison: equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator==(const const_iterator& other) const + { + // if objects are not the same, the comparison is undefined + if (m_object != other.m_object) + { + throw std::domain_error("cannot compare iterators of different containers"); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + return (m_it.object_iterator == other.m_it.object_iterator); + } + + case basic_json::value_t::array: + { + return (m_it.array_iterator == other.m_it.array_iterator); + } + + default: + { + return (m_it.primitive_iterator == other.m_it.primitive_iterator); + } + } + } + + /*! + @brief comparison: not equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator!=(const const_iterator& other) const + { + return not operator==(other); + } + + /*! + @brief comparison: smaller + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<(const const_iterator& other) const + { + // if objects are not the same, the comparison is undefined + if (m_object != other.m_object) + { + throw std::domain_error("cannot compare iterators of different containers"); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + throw std::domain_error("cannot compare order of object iterators"); + } + + case basic_json::value_t::array: + { + return (m_it.array_iterator < other.m_it.array_iterator); + } + + default: + { + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } + } + + /*! + @brief comparison: less than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<=(const const_iterator& other) const + { + return not other.operator < (*this); + } + + /*! + @brief comparison: greater than + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>(const const_iterator& other) const + { + return not operator<=(other); + } + + /*! + @brief comparison: greater than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>=(const const_iterator& other) const + { + return not operator<(other); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const_iterator& operator+=(difference_type i) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + throw std::domain_error("cannot use offsets with object iterators"); + } + + case basic_json::value_t::array: + { + std::advance(m_it.array_iterator, i); + break; + } + + default: + { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const_iterator& operator-=(difference_type i) + { + return operator+=(-i); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const_iterator operator+(difference_type i) + { + auto result = *this; + result += i; + return result; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const_iterator operator-(difference_type i) + { + auto result = *this; + result -= i; + return result; + } + + /*! + @brief return difference + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + difference_type operator-(const const_iterator& other) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + throw std::domain_error("cannot use offsets with object iterators"); + } + + case basic_json::value_t::array: + { + return m_it.array_iterator - other.m_it.array_iterator; + } + + default: + { + return m_it.primitive_iterator - other.m_it.primitive_iterator; + } + } + } + + /*! + @brief access to successor + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator[](difference_type n) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + throw std::domain_error("cannot use operator[] for object iterators"); + } + + case basic_json::value_t::array: + { + return *std::next(m_it.array_iterator, n); + } + + case basic_json::value_t::null: + { + throw std::out_of_range("cannot get value"); + } + + default: + { + if (m_it.primitive_iterator == -n) + { + return *m_object; + } + else + { + throw std::out_of_range("cannot get value"); + } + } + } + } + + /*! + @brief return the key of an object iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + typename object_t::key_type key() const + { + assert(m_object != nullptr); + + if (m_object->is_object()) + { + return m_it.object_iterator->first; + } + else + { + throw std::domain_error("cannot use key() for non-object iterators"); + } + } + + /*! + @brief return the value of an iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference value() const + { + return operator*(); + } + + private: + /// associated JSON instance + pointer m_object = nullptr; + /// the actual iterator of the associated instance + internal_iterator m_it = internal_iterator(); + }; + + /*! + @brief a mutable random access iterator for the @ref basic_json class + + @requirement The class satisfies the following concept requirements: + - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): + The iterator that can be moved to point (forward and backward) to any + element in constant time. + - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): + It is possible to write to the pointed-to element. + + @since version 1.0.0 + */ + class iterator : public const_iterator + { + public: + using base_iterator = const_iterator; + using pointer = typename basic_json::pointer; + using reference = typename basic_json::reference; + + /// default constructor + iterator() = default; + + /// constructor for a given JSON instance + explicit iterator(pointer object) noexcept + : base_iterator(object) + {} + + /// copy constructor + iterator(const iterator& other) noexcept + : base_iterator(other) + {} + + /// copy assignment + iterator& operator=(iterator other) noexcept( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + base_iterator::operator=(other); + return *this; + } + + /// return a reference to the value pointed to by the iterator + reference operator*() const + { + return const_cast(base_iterator::operator*()); + } + + /// dereference the iterator + pointer operator->() const + { + return const_cast(base_iterator::operator->()); + } + + /// post-increment (it++) + iterator operator++(int) + { + iterator result = *this; + base_iterator::operator++(); + return result; + } + + /// pre-increment (++it) + iterator& operator++() + { + base_iterator::operator++(); + return *this; + } + + /// post-decrement (it--) + iterator operator--(int) + { + iterator result = *this; + base_iterator::operator--(); + return result; + } + + /// pre-decrement (--it) + iterator& operator--() + { + base_iterator::operator--(); + return *this; + } + + /// add to iterator + iterator& operator+=(difference_type i) + { + base_iterator::operator+=(i); + return *this; + } + + /// subtract from iterator + iterator& operator-=(difference_type i) + { + base_iterator::operator-=(i); + return *this; + } + + /// add to iterator + iterator operator+(difference_type i) + { + auto result = *this; + result += i; + return result; + } + + /// subtract from iterator + iterator operator-(difference_type i) + { + auto result = *this; + result -= i; + return result; + } + + /// return difference + difference_type operator-(const iterator& other) const + { + return base_iterator::operator-(other); + } + + /// access to successor + reference operator[](difference_type n) const + { + return const_cast(base_iterator::operator[](n)); + } + + /// return the value of an iterator + reference value() const + { + return const_cast(base_iterator::value()); + } + }; + + /*! + @brief a template for a reverse iterator class + + @tparam Base the base iterator type to reverse. Valid types are @ref + iterator (to create @ref reverse_iterator) and @ref const_iterator (to + create @ref const_reverse_iterator). + + @requirement The class satisfies the following concept requirements: + - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): + The iterator that can be moved to point (forward and backward) to any + element in constant time. + - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): + It is possible to write to the pointed-to element (only if @a Base is + @ref iterator). + + @since version 1.0.0 + */ + template + class json_reverse_iterator : public std::reverse_iterator + { + public: + /// shortcut to the reverse iterator adaptor + using base_iterator = std::reverse_iterator; + /// the reference type for the pointed-to element + using reference = typename Base::reference; + + /// create reverse iterator from iterator + json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept + : base_iterator(it) + {} + + /// create reverse iterator from base class + json_reverse_iterator(const base_iterator& it) noexcept + : base_iterator(it) + {} + + /// post-increment (it++) + json_reverse_iterator operator++(int) + { + return base_iterator::operator++(1); + } + + /// pre-increment (++it) + json_reverse_iterator& operator++() + { + base_iterator::operator++(); + return *this; + } + + /// post-decrement (it--) + json_reverse_iterator operator--(int) + { + return base_iterator::operator--(1); + } + + /// pre-decrement (--it) + json_reverse_iterator& operator--() + { + base_iterator::operator--(); + return *this; + } + + /// add to iterator + json_reverse_iterator& operator+=(difference_type i) + { + base_iterator::operator+=(i); + return *this; + } + + /// add to iterator + json_reverse_iterator operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /// subtract from iterator + json_reverse_iterator operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /// return difference + difference_type operator-(const json_reverse_iterator& other) const + { + return this->base() - other.base(); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } + + /// return the key of an object iterator + typename object_t::key_type key() const + { + auto it = --this->base(); + return it.key(); + } + + /// return the value of an iterator + reference value() const + { + auto it = --this->base(); + return it.operator * (); + } + }; + + + private: + ////////////////////// + // lexer and parser // + ////////////////////// + + /*! + @brief lexical analysis + + This class organizes the lexical analysis during JSON deserialization. The + core of it is a scanner generated by [re2c](http://re2c.org) that + processes a buffer and recognizes tokens according to RFC 7159. + */ + class lexer + { + public: + /// token types for the parser + enum class token_type + { + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_number, ///< a number -- use get_number() for actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` + parse_error, ///< indicating a parse error + end_of_input ///< indicating the end of the input buffer + }; + + /// the char type to use in the lexer + using lexer_char_t = unsigned char; + + /// constructor with a given buffer + explicit lexer(const string_t& s) noexcept + : m_stream(nullptr), m_buffer(s) + { + m_content = reinterpret_cast(m_buffer.c_str()); + assert(m_content != nullptr); + m_start = m_cursor = m_content; + m_limit = m_content + s.size(); + } + + /// constructor with a given stream + explicit lexer(std::istream* s) noexcept + : m_stream(s), m_buffer() + { + assert(m_stream != nullptr); + std::getline(*m_stream, m_buffer); + m_content = reinterpret_cast(m_buffer.c_str()); + assert(m_content != nullptr); + m_start = m_cursor = m_content; + m_limit = m_content + m_buffer.size(); + } + + /// default constructor + lexer() = default; + + // switch off unwanted functions + lexer(const lexer&) = delete; + lexer operator=(const lexer&) = delete; + + /*! + @brief create a string from one or two Unicode code points + + There are two cases: (1) @a codepoint1 is in the Basic Multilingual + Plane (U+0000 through U+FFFF) and @a codepoint2 is 0, or (2) + @a codepoint1 and @a codepoint2 are a UTF-16 surrogate pair to + represent a code point above U+FFFF. + + @param[in] codepoint1 the code point (can be high surrogate) + @param[in] codepoint2 the code point (can be low surrogate or 0) + + @return string representation of the code point; the length of the + result string is between 1 and 4 characters. + + @throw std::out_of_range if code point is > 0x10ffff; example: `"code + points above 0x10FFFF are invalid"` + @throw std::invalid_argument if the low surrogate is invalid; example: + `""missing or wrong low surrogate""` + + @complexity Constant. + + @see + */ + static string_t to_unicode(const std::size_t codepoint1, + const std::size_t codepoint2 = 0) + { + // calculate the code point from the given code points + std::size_t codepoint = codepoint1; + + // check if codepoint1 is a high surrogate + if (codepoint1 >= 0xD800 and codepoint1 <= 0xDBFF) + { + // check if codepoint2 is a low surrogate + if (codepoint2 >= 0xDC00 and codepoint2 <= 0xDFFF) + { + codepoint = + // high surrogate occupies the most significant 22 bits + (codepoint1 << 10) + // low surrogate occupies the least significant 15 bits + + codepoint2 + // there is still the 0xD800, 0xDC00 and 0x10000 noise + // in the result so we have to subtract with: + // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 + - 0x35FDC00; + } + else + { + throw std::invalid_argument("missing or wrong low surrogate"); + } + } + + string_t result; + + if (codepoint < 0x80) + { + // 1-byte characters: 0xxxxxxx (ASCII) + result.append(1, static_cast(codepoint)); + } + else if (codepoint <= 0x7ff) + { + // 2-byte characters: 110xxxxx 10xxxxxx + result.append(1, static_cast(0xC0 | ((codepoint >> 6) & 0x1F))); + result.append(1, static_cast(0x80 | (codepoint & 0x3F))); + } + else if (codepoint <= 0xffff) + { + // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx + result.append(1, static_cast(0xE0 | ((codepoint >> 12) & 0x0F))); + result.append(1, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + result.append(1, static_cast(0x80 | (codepoint & 0x3F))); + } + else if (codepoint <= 0x10ffff) + { + // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + result.append(1, static_cast(0xF0 | ((codepoint >> 18) & 0x07))); + result.append(1, static_cast(0x80 | ((codepoint >> 12) & 0x3F))); + result.append(1, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + result.append(1, static_cast(0x80 | (codepoint & 0x3F))); + } + else + { + throw std::out_of_range("code points above 0x10FFFF are invalid"); + } + + return result; + } + + /// return name of values of type token_type (only used for errors) + static std::string token_type_name(const token_type t) + { + switch (t) + { + case token_type::uninitialized: + return ""; + case token_type::literal_true: + return "true literal"; + case token_type::literal_false: + return "false literal"; + case token_type::literal_null: + return "null literal"; + case token_type::value_string: + return "string literal"; + case token_type::value_number: + return "number literal"; + case token_type::begin_array: + return "'['"; + case token_type::begin_object: + return "'{'"; + case token_type::end_array: + return "']'"; + case token_type::end_object: + return "'}'"; + case token_type::name_separator: + return "':'"; + case token_type::value_separator: + return "','"; + case token_type::parse_error: + return ""; + case token_type::end_of_input: + return "end of input"; + default: + { + // catch non-enum values + return "unknown token"; // LCOV_EXCL_LINE + } + } + } + + /*! + This function implements a scanner for JSON. It is specified using + regular expressions that try to follow RFC 7159 as close as possible. + These regular expressions are then translated into a minimized + deterministic finite automaton (DFA) by the tool + [re2c](http://re2c.org). As a result, the translated code for this + function consists of a large block of code with `goto` jumps. + + @return the class of the next token read from the buffer + + @complexity Linear in the length of the input.\n + + Proposition: The loop below will always terminate for finite input.\n + + Proof (by contradiction): Assume a finite input. To loop forever, the + loop must never hit code with a `break` statement. The only code + snippets without a `break` statement are the continue statements for + whitespace and byte-order-marks. To loop forever, the input must be an + infinite sequence of whitespace or byte-order-marks. This contradicts + the assumption of finite input, q.e.d. + */ + token_type scan() noexcept + { + while (true) + { + // pointer for backtracking information + m_marker = nullptr; + + // remember the begin of the token + m_start = m_cursor; + assert(m_start != nullptr); + + + { + lexer_char_t yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = + { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 32, 32, 0, 0, 32, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 160, 128, 0, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 0, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + }; + if ((m_limit - m_cursor) < 5) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yybm[0 + yych] & 32) + { + goto basic_json_parser_6; + } + if (yych <= '\\') + { + if (yych <= '-') + { + if (yych <= '"') + { + if (yych <= 0x00) + { + goto basic_json_parser_2; + } + if (yych <= '!') + { + goto basic_json_parser_4; + } + goto basic_json_parser_9; + } + else + { + if (yych <= '+') + { + goto basic_json_parser_4; + } + if (yych <= ',') + { + goto basic_json_parser_10; + } + goto basic_json_parser_12; + } + } + else + { + if (yych <= '9') + { + if (yych <= '/') + { + goto basic_json_parser_4; + } + if (yych <= '0') + { + goto basic_json_parser_13; + } + goto basic_json_parser_15; + } + else + { + if (yych <= ':') + { + goto basic_json_parser_17; + } + if (yych == '[') + { + goto basic_json_parser_19; + } + goto basic_json_parser_4; + } + } + } + else + { + if (yych <= 't') + { + if (yych <= 'f') + { + if (yych <= ']') + { + goto basic_json_parser_21; + } + if (yych <= 'e') + { + goto basic_json_parser_4; + } + goto basic_json_parser_23; + } + else + { + if (yych == 'n') + { + goto basic_json_parser_24; + } + if (yych <= 's') + { + goto basic_json_parser_4; + } + goto basic_json_parser_25; + } + } + else + { + if (yych <= '|') + { + if (yych == '{') + { + goto basic_json_parser_26; + } + goto basic_json_parser_4; + } + else + { + if (yych <= '}') + { + goto basic_json_parser_28; + } + if (yych == 0xEF) + { + goto basic_json_parser_30; + } + goto basic_json_parser_4; + } + } + } +basic_json_parser_2: + ++m_cursor; + { + last_token_type = token_type::end_of_input; + break; + } +basic_json_parser_4: + ++m_cursor; +basic_json_parser_5: + { + last_token_type = token_type::parse_error; + break; + } +basic_json_parser_6: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yybm[0 + yych] & 32) + { + goto basic_json_parser_6; + } + { + continue; + } +basic_json_parser_9: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych <= 0x1F) + { + goto basic_json_parser_5; + } + goto basic_json_parser_32; +basic_json_parser_10: + ++m_cursor; + { + last_token_type = token_type::value_separator; + break; + } +basic_json_parser_12: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_5; + } + if (yych <= '0') + { + goto basic_json_parser_13; + } + if (yych <= '9') + { + goto basic_json_parser_15; + } + goto basic_json_parser_5; +basic_json_parser_13: + yyaccept = 1; + yych = *(m_marker = ++m_cursor); + if (yych <= 'D') + { + if (yych == '.') + { + goto basic_json_parser_37; + } + } + else + { + if (yych <= 'E') + { + goto basic_json_parser_38; + } + if (yych == 'e') + { + goto basic_json_parser_38; + } + } +basic_json_parser_14: + { + last_token_type = token_type::value_number; + break; + } +basic_json_parser_15: + yyaccept = 1; + m_marker = ++m_cursor; + if ((m_limit - m_cursor) < 3) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yybm[0 + yych] & 64) + { + goto basic_json_parser_15; + } + if (yych <= 'D') + { + if (yych == '.') + { + goto basic_json_parser_37; + } + goto basic_json_parser_14; + } + else + { + if (yych <= 'E') + { + goto basic_json_parser_38; + } + if (yych == 'e') + { + goto basic_json_parser_38; + } + goto basic_json_parser_14; + } +basic_json_parser_17: + ++m_cursor; + { + last_token_type = token_type::name_separator; + break; + } +basic_json_parser_19: + ++m_cursor; + { + last_token_type = token_type::begin_array; + break; + } +basic_json_parser_21: + ++m_cursor; + { + last_token_type = token_type::end_array; + break; + } +basic_json_parser_23: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'a') + { + goto basic_json_parser_39; + } + goto basic_json_parser_5; +basic_json_parser_24: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'u') + { + goto basic_json_parser_40; + } + goto basic_json_parser_5; +basic_json_parser_25: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'r') + { + goto basic_json_parser_41; + } + goto basic_json_parser_5; +basic_json_parser_26: + ++m_cursor; + { + last_token_type = token_type::begin_object; + break; + } +basic_json_parser_28: + ++m_cursor; + { + last_token_type = token_type::end_object; + break; + } +basic_json_parser_30: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 0xBB) + { + goto basic_json_parser_42; + } + goto basic_json_parser_5; +basic_json_parser_31: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; +basic_json_parser_32: + if (yybm[0 + yych] & 128) + { + goto basic_json_parser_31; + } + if (yych <= 0x1F) + { + goto basic_json_parser_33; + } + if (yych <= '"') + { + goto basic_json_parser_34; + } + goto basic_json_parser_36; +basic_json_parser_33: + m_cursor = m_marker; + if (yyaccept == 0) + { + goto basic_json_parser_5; + } + else + { + goto basic_json_parser_14; + } +basic_json_parser_34: + ++m_cursor; + { + last_token_type = token_type::value_string; + break; + } +basic_json_parser_36: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= 'e') + { + if (yych <= '/') + { + if (yych == '"') + { + goto basic_json_parser_31; + } + if (yych <= '.') + { + goto basic_json_parser_33; + } + goto basic_json_parser_31; + } + else + { + if (yych <= '\\') + { + if (yych <= '[') + { + goto basic_json_parser_33; + } + goto basic_json_parser_31; + } + else + { + if (yych == 'b') + { + goto basic_json_parser_31; + } + goto basic_json_parser_33; + } + } + } + else + { + if (yych <= 'q') + { + if (yych <= 'f') + { + goto basic_json_parser_31; + } + if (yych == 'n') + { + goto basic_json_parser_31; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 's') + { + if (yych <= 'r') + { + goto basic_json_parser_31; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 't') + { + goto basic_json_parser_31; + } + if (yych <= 'u') + { + goto basic_json_parser_43; + } + goto basic_json_parser_33; + } + } + } +basic_json_parser_37: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_44; + } + goto basic_json_parser_33; +basic_json_parser_38: + yych = *++m_cursor; + if (yych <= ',') + { + if (yych == '+') + { + goto basic_json_parser_46; + } + goto basic_json_parser_33; + } + else + { + if (yych <= '-') + { + goto basic_json_parser_46; + } + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_47; + } + goto basic_json_parser_33; + } +basic_json_parser_39: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_49; + } + goto basic_json_parser_33; +basic_json_parser_40: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_50; + } + goto basic_json_parser_33; +basic_json_parser_41: + yych = *++m_cursor; + if (yych == 'u') + { + goto basic_json_parser_51; + } + goto basic_json_parser_33; +basic_json_parser_42: + yych = *++m_cursor; + if (yych == 0xBF) + { + goto basic_json_parser_52; + } + goto basic_json_parser_33; +basic_json_parser_43: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_54; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_54; + } + if (yych <= '`') + { + goto basic_json_parser_33; + } + if (yych <= 'f') + { + goto basic_json_parser_54; + } + goto basic_json_parser_33; + } +basic_json_parser_44: + yyaccept = 1; + m_marker = ++m_cursor; + if ((m_limit - m_cursor) < 3) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= 'D') + { + if (yych <= '/') + { + goto basic_json_parser_14; + } + if (yych <= '9') + { + goto basic_json_parser_44; + } + goto basic_json_parser_14; + } + else + { + if (yych <= 'E') + { + goto basic_json_parser_38; + } + if (yych == 'e') + { + goto basic_json_parser_38; + } + goto basic_json_parser_14; + } +basic_json_parser_46: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych >= ':') + { + goto basic_json_parser_33; + } +basic_json_parser_47: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= '/') + { + goto basic_json_parser_14; + } + if (yych <= '9') + { + goto basic_json_parser_47; + } + goto basic_json_parser_14; +basic_json_parser_49: + yych = *++m_cursor; + if (yych == 's') + { + goto basic_json_parser_55; + } + goto basic_json_parser_33; +basic_json_parser_50: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_56; + } + goto basic_json_parser_33; +basic_json_parser_51: + yych = *++m_cursor; + if (yych == 'e') + { + goto basic_json_parser_58; + } + goto basic_json_parser_33; +basic_json_parser_52: + ++m_cursor; + { + continue; + } +basic_json_parser_54: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_60; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_60; + } + if (yych <= '`') + { + goto basic_json_parser_33; + } + if (yych <= 'f') + { + goto basic_json_parser_60; + } + goto basic_json_parser_33; + } +basic_json_parser_55: + yych = *++m_cursor; + if (yych == 'e') + { + goto basic_json_parser_61; + } + goto basic_json_parser_33; +basic_json_parser_56: + ++m_cursor; + { + last_token_type = token_type::literal_null; + break; + } +basic_json_parser_58: + ++m_cursor; + { + last_token_type = token_type::literal_true; + break; + } +basic_json_parser_60: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_63; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_63; + } + if (yych <= '`') + { + goto basic_json_parser_33; + } + if (yych <= 'f') + { + goto basic_json_parser_63; + } + goto basic_json_parser_33; + } +basic_json_parser_61: + ++m_cursor; + { + last_token_type = token_type::literal_false; + break; + } +basic_json_parser_63: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_31; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_31; + } + if (yych <= '`') + { + goto basic_json_parser_33; + } + if (yych <= 'f') + { + goto basic_json_parser_31; + } + goto basic_json_parser_33; + } + } + + } + + return last_token_type; + } + + /// append data from the stream to the internal buffer + void yyfill() noexcept + { + if (m_stream == nullptr or not * m_stream) + { + return; + } + + const auto offset_start = m_start - m_content; + const auto offset_marker = m_marker - m_start; + const auto offset_cursor = m_cursor - m_start; + + m_buffer.erase(0, static_cast(offset_start)); + std::string line; + assert(m_stream != nullptr); + std::getline(*m_stream, line); + m_buffer += "\n" + line; // add line with newline symbol + + m_content = reinterpret_cast(m_buffer.c_str()); + assert(m_content != nullptr); + m_start = m_content; + m_marker = m_start + offset_marker; + m_cursor = m_start + offset_cursor; + m_limit = m_start + m_buffer.size() - 1; + } + + /// return string representation of last read token + string_t get_token_string() const + { + assert(m_start != nullptr); + return string_t(reinterpret_cast(m_start), + static_cast(m_cursor - m_start)); + } + + /*! + @brief return string value for string tokens + + The function iterates the characters between the opening and closing + quotes of the string value. The complete string is the range + [m_start,m_cursor). Consequently, we iterate from m_start+1 to + m_cursor-1. + + We differentiate two cases: + + 1. Escaped characters. In this case, a new character is constructed + according to the nature of the escape. Some escapes create new + characters (e.g., `"\\n"` is replaced by `"\n"`), some are copied + as is (e.g., `"\\\\"`). Furthermore, Unicode escapes of the shape + `"\\uxxxx"` need special care. In this case, to_unicode takes care + of the construction of the values. + 2. Unescaped characters are copied as is. + + @pre `m_cursor - m_start >= 2`, meaning the length of the last token + is at least 2 bytes which is trivially true for any string (which + consists of at least two quotes). + + " c1 c2 c3 ... " + ^ ^ + m_start m_cursor + + @complexity Linear in the length of the string.\n + + Lemma: The loop body will always terminate.\n + + Proof (by contradiction): Assume the loop body does not terminate. As + the loop body does not contain another loop, one of the called + functions must never return. The called functions are `std::strtoul` + and to_unicode. Neither function can loop forever, so the loop body + will never loop forever which contradicts the assumption that the loop + body does not terminate, q.e.d.\n + + Lemma: The loop condition for the for loop is eventually false.\n + + Proof (by contradiction): Assume the loop does not terminate. Due to + the above lemma, this can only be due to a tautological loop + condition; that is, the loop condition i < m_cursor - 1 must always be + true. Let x be the change of i for any loop iteration. Then + m_start + 1 + x < m_cursor - 1 must hold to loop indefinitely. This + can be rephrased to m_cursor - m_start - 2 > x. With the + precondition, we x <= 0, meaning that the loop condition holds + indefinitly if i is always decreased. However, observe that the value + of i is strictly increasing with each iteration, as it is incremented + by 1 in the iteration expression and never decremented inside the loop + body. Hence, the loop condition will eventually be false which + contradicts the assumption that the loop condition is a tautology, + q.e.d. + + @return string value of current token without opening and closing + quotes + @throw std::out_of_range if to_unicode fails + */ + string_t get_string() const + { + assert(m_cursor - m_start >= 2); + + string_t result; + result.reserve(static_cast(m_cursor - m_start - 2)); + + // iterate the result between the quotes + for (const lexer_char_t* i = m_start + 1; i < m_cursor - 1; ++i) + { + // process escaped characters + if (*i == '\\') + { + // read next character + ++i; + + switch (*i) + { + // the default escapes + case 't': + { + result += "\t"; + break; + } + case 'b': + { + result += "\b"; + break; + } + case 'f': + { + result += "\f"; + break; + } + case 'n': + { + result += "\n"; + break; + } + case 'r': + { + result += "\r"; + break; + } + case '\\': + { + result += "\\"; + break; + } + case '/': + { + result += "/"; + break; + } + case '"': + { + result += "\""; + break; + } + + // unicode + case 'u': + { + // get code xxxx from uxxxx + auto codepoint = std::strtoul(std::string(reinterpret_cast(i + 1), + 4).c_str(), nullptr, 16); + + // check if codepoint is a high surrogate + if (codepoint >= 0xD800 and codepoint <= 0xDBFF) + { + // make sure there is a subsequent unicode + if ((i + 6 >= m_limit) or * (i + 5) != '\\' or * (i + 6) != 'u') + { + throw std::invalid_argument("missing low surrogate"); + } + + // get code yyyy from uxxxx\uyyyy + auto codepoint2 = std::strtoul(std::string(reinterpret_cast + (i + 7), 4).c_str(), nullptr, 16); + result += to_unicode(codepoint, codepoint2); + // skip the next 10 characters (xxxx\uyyyy) + i += 10; + } + else + { + // add unicode character(s) + result += to_unicode(codepoint); + // skip the next four characters (xxxx) + i += 4; + } + break; + } + } + } + else + { + // all other characters are just copied to the end of the + // string + result.append(1, static_cast(*i)); + } + } + + return result; + } + + /*! + @brief parse floating point number + + This function (and its overloads) serves to select the most approprate + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to @a + static_cast(nullptr). + + @param[in] type the @ref number_float_t in use + + @param[in,out] endptr recieves a pointer to the first character after + the number + + @return the floating point number + */ + long double str_to_float_t(long double* /* type */, char** endptr) const + { + return std::strtold(reinterpret_cast(m_start), endptr); + } + + /*! + @brief parse floating point number + + This function (and its overloads) serves to select the most approprate + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to @a + static_cast(nullptr). + + @param[in] type the @ref number_float_t in use + + @param[in,out] endptr recieves a pointer to the first character after + the number + + @return the floating point number + */ + double str_to_float_t(double* /* type */, char** endptr) const + { + return std::strtod(reinterpret_cast(m_start), endptr); + } + + /*! + @brief parse floating point number + + This function (and its overloads) serves to select the most approprate + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to @a + static_cast(nullptr). + + @param[in] type the @ref number_float_t in use + + @param[in,out] endptr recieves a pointer to the first character after + the number + + @return the floating point number + */ + float str_to_float_t(float* /* type */, char** endptr) const + { + return std::strtof(reinterpret_cast(m_start), endptr); + } + + /*! + @brief return number value for number tokens + + This function translates the last token into the most appropriate + number type (either integer, unsigned integer or floating point), + which is passed back to the caller via the result parameter. + + This function parses the integer component up to the radix point or + exponent while collecting information about the 'floating point + representation', which it stores in the result parameter. If there is + no radix point or exponent, and the number can fit into a @ref + number_integer_t or @ref number_unsigned_t then it sets the result + parameter accordingly. + + If the number is a floating point number the number is then parsed + using @a std:strtod (or @a std:strtof or @a std::strtold). + + @param[out] result @ref basic_json object to receive the number, or + NAN if the conversion read past the current token. The latter case + needs to be treated by the caller function. + */ + void get_number(basic_json& result) const + { + assert(m_start != nullptr); + + const lexer::lexer_char_t* curptr = m_start; + + // accumulate the integer conversion result (unsigned for now) + number_unsigned_t value = 0; + + // maximum absolute value of the relevant integer type + number_unsigned_t max; + + // temporarily store the type to avoid unecessary bitfield access + value_t type; + + // look for sign + if (*curptr == '-') + { + type = value_t::number_integer; + max = static_cast((std::numeric_limits::max)()) + 1; + curptr++; + } + else + { + type = value_t::number_unsigned; + max = static_cast((std::numeric_limits::max)()); + } + + // count the significant figures + for (; curptr < m_cursor; curptr++) + { + // quickly skip tests if a digit + if (*curptr < '0' || *curptr > '9') + { + if (*curptr == '.') + { + // don't count '.' but change to float + type = value_t::number_float; + continue; + } + // assume exponent (if not then will fail parse): change to + // float, stop counting and record exponent details + type = value_t::number_float; + break; + } + + // skip if definitely not an integer + if (type != value_t::number_float) + { + // multiply last value by ten and add the new digit + auto temp = value * 10 + *curptr - '0'; + + // test for overflow + if (temp < value || temp > max) + { + // overflow + type = value_t::number_float; + } + else + { + // no overflow - save it + value = temp; + } + } + } + + // save the value (if not a float) + if (type == value_t::number_unsigned) + { + result.m_value.number_unsigned = value; + } + else if (type == value_t::number_integer) + { + result.m_value.number_integer = -static_cast(value); + } + else + { + // parse with strtod + result.m_value.number_float = str_to_float_t(static_cast(nullptr), NULL); + } + + // save the type + result.m_type = type; + } + + private: + /// optional input stream + std::istream* m_stream = nullptr; + /// the buffer + string_t m_buffer; + /// the buffer pointer + const lexer_char_t* m_content = nullptr; + /// pointer to the beginning of the current symbol + const lexer_char_t* m_start = nullptr; + /// pointer for backtracking information + const lexer_char_t* m_marker = nullptr; + /// pointer to the current symbol + const lexer_char_t* m_cursor = nullptr; + /// pointer to the end of the buffer + const lexer_char_t* m_limit = nullptr; + /// the last token type + token_type last_token_type = token_type::end_of_input; + }; + + /*! + @brief syntax analysis + + This class implements a recursive decent parser. + */ + class parser + { + public: + /// constructor for strings + parser(const string_t& s, const parser_callback_t cb = nullptr) noexcept + : callback(cb), m_lexer(s) + { + // read first token + get_token(); + } + + /// a parser reading from an input stream + parser(std::istream& _is, const parser_callback_t cb = nullptr) noexcept + : callback(cb), m_lexer(&_is) + { + // read first token + get_token(); + } + + /// public parser interface + basic_json parse() + { + basic_json result = parse_internal(true); + result.assert_invariant(); + + expect(lexer::token_type::end_of_input); + + // return parser result and replace it with null in case the + // top-level value was discarded by the callback function + return result.is_discarded() ? basic_json() : std::move(result); + } + + private: + /// the actual parser + basic_json parse_internal(bool keep) + { + auto result = basic_json(value_t::discarded); + + switch (last_token) + { + case lexer::token_type::begin_object: + { + if (keep and (not callback or (keep = callback(depth++, parse_event_t::object_start, result)))) + { + // explicitly set result to object to cope with {} + result.m_type = value_t::object; + result.m_value = value_t::object; + } + + // read next token + get_token(); + + // closing } -> we are done + if (last_token == lexer::token_type::end_object) + { + get_token(); + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result = basic_json(value_t::discarded); + } + return result; + } + + // no comma is expected here + unexpect(lexer::token_type::value_separator); + + // otherwise: parse key-value pairs + do + { + // ugly, but could be fixed with loop reorganization + if (last_token == lexer::token_type::value_separator) + { + get_token(); + } + + // store key + expect(lexer::token_type::value_string); + const auto key = m_lexer.get_string(); + + bool keep_tag = false; + if (keep) + { + if (callback) + { + basic_json k(key); + keep_tag = callback(depth, parse_event_t::key, k); + } + else + { + keep_tag = true; + } + } + + // parse separator (:) + get_token(); + expect(lexer::token_type::name_separator); + + // parse and add value + get_token(); + auto value = parse_internal(keep); + if (keep and keep_tag and not value.is_discarded()) + { + result[key] = std::move(value); + } + } + while (last_token == lexer::token_type::value_separator); + + // closing } + expect(lexer::token_type::end_object); + get_token(); + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result = basic_json(value_t::discarded); + } + + return result; + } + + case lexer::token_type::begin_array: + { + if (keep and (not callback or (keep = callback(depth++, parse_event_t::array_start, result)))) + { + // explicitly set result to object to cope with [] + result.m_type = value_t::array; + result.m_value = value_t::array; + } + + // read next token + get_token(); + + // closing ] -> we are done + if (last_token == lexer::token_type::end_array) + { + get_token(); + if (callback and not callback(--depth, parse_event_t::array_end, result)) + { + result = basic_json(value_t::discarded); + } + return result; + } + + // no comma is expected here + unexpect(lexer::token_type::value_separator); + + // otherwise: parse values + do + { + // ugly, but could be fixed with loop reorganization + if (last_token == lexer::token_type::value_separator) + { + get_token(); + } + + // parse value + auto value = parse_internal(keep); + if (keep and not value.is_discarded()) + { + result.push_back(std::move(value)); + } + } + while (last_token == lexer::token_type::value_separator); + + // closing ] + expect(lexer::token_type::end_array); + get_token(); + if (keep and callback and not callback(--depth, parse_event_t::array_end, result)) + { + result = basic_json(value_t::discarded); + } + + return result; + } + + case lexer::token_type::literal_null: + { + get_token(); + result.m_type = value_t::null; + break; + } + + case lexer::token_type::value_string: + { + const auto s = m_lexer.get_string(); + get_token(); + result = basic_json(s); + break; + } + + case lexer::token_type::literal_true: + { + get_token(); + result.m_type = value_t::boolean; + result.m_value = true; + break; + } + + case lexer::token_type::literal_false: + { + get_token(); + result.m_type = value_t::boolean; + result.m_value = false; + break; + } + + case lexer::token_type::value_number: + { + m_lexer.get_number(result); + get_token(); + break; + } + + default: + { + // the last token was unexpected + unexpect(last_token); + } + } + + if (keep and callback and not callback(depth, parse_event_t::value, result)) + { + result = basic_json(value_t::discarded); + } + return result; + } + + /// get next token from lexer + typename lexer::token_type get_token() noexcept + { + last_token = m_lexer.scan(); + return last_token; + } + + void expect(typename lexer::token_type t) const + { + if (t != last_token) + { + std::string error_msg = "parse error - unexpected "; + error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token_string() + + "'") : + lexer::token_type_name(last_token)); + error_msg += "; expected " + lexer::token_type_name(t); + throw std::invalid_argument(error_msg); + } + } + + void unexpect(typename lexer::token_type t) const + { + if (t == last_token) + { + std::string error_msg = "parse error - unexpected "; + error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token_string() + + "'") : + lexer::token_type_name(last_token)); + throw std::invalid_argument(error_msg); + } + } + + private: + /// current level of recursion + int depth = 0; + /// callback function + const parser_callback_t callback = nullptr; + /// the type of the last read token + typename lexer::token_type last_token = lexer::token_type::uninitialized; + /// the lexer + lexer m_lexer; + }; + + public: + /*! + @brief JSON Pointer + + A JSON pointer defines a string syntax for identifying a specific value + within a JSON document. It can be used with functions `at` and + `operator[]`. Furthermore, JSON pointers are the base for JSON patches. + + @sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + class json_pointer + { + /// allow basic_json to access private members + friend class basic_json; + + public: + /*! + @brief create JSON pointer + + Create a JSON pointer according to the syntax described in + [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). + + @param[in] s string representing the JSON pointer; if omitted, the + empty string is assumed which references the whole JSON + value + + @throw std::domain_error if reference token is nonempty and does not + begin with a slash (`/`); example: `"JSON pointer must be empty or + begin with /"` + @throw std::domain_error if a tilde (`~`) is not followed by `0` + (representing `~`) or `1` (representing `/`); example: `"escape error: + ~ must be followed with 0 or 1"` + + @liveexample{The example shows the construction several valid JSON + pointers as well as the exceptional behavior.,json_pointer} + + @since version 2.0.0 + */ + explicit json_pointer(const std::string& s = "") + : reference_tokens(split(s)) + {} + + /*! + @brief return a string representation of the JSON pointer + + @invariant For each JSON pointer `ptr`, it holds: + @code {.cpp} + ptr == json_pointer(ptr.to_string()); + @endcode + + @return a string representation of the JSON pointer + + @liveexample{The example shows the result of `to_string`., + json_pointer__to_string} + + @since version 2.0.0 + */ + std::string to_string() const noexcept + { + return std::accumulate(reference_tokens.begin(), + reference_tokens.end(), std::string{}, + [](const std::string & a, const std::string & b) + { + return a + "/" + escape(b); + }); + } + + /// @copydoc to_string() + operator std::string() const + { + return to_string(); + } + + private: + /// remove and return last reference pointer + std::string pop_back() + { + if (is_root()) + { + throw std::domain_error("JSON pointer has no parent"); + } + + auto last = reference_tokens.back(); + reference_tokens.pop_back(); + return last; + } + + /// return whether pointer points to the root document + bool is_root() const + { + return reference_tokens.empty(); + } + + json_pointer top() const + { + if (is_root()) + { + throw std::domain_error("JSON pointer has no parent"); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + /*! + @brief create and return a reference to the pointed to value + + @complexity Linear in the number of reference tokens. + */ + reference get_and_create(reference j) const + { + pointer result = &j; + + // in case no reference tokens exist, return a reference to the + // JSON value j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->m_type) + { + case value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case value_t::array: + { + // create an entry in the array + result = &result->operator[](static_cast(std::stoi(reference_token))); + break; + } + + /* + The following code is only reached if there exists a + reference token _and_ the current value is primitive. In + this case, we have an error situation, because primitive + values may only occur as single value; that is, with an + empty list of reference tokens. + */ + default: + { + throw std::domain_error("invalid value to unflatten"); + } + } + } + + return *result; + } + + /*! + @brief return a reference to the pointed to value + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + */ + reference get_unchecked(pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case value_t::array: + { + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + if (reference_token == "-") + { + // explicityly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); + } + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + reference get_checked(pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" always fails the range check + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // note: at performs range check + ptr = &ptr->at(static_cast(std::stoi(reference_token))); + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + */ + const_reference get_unchecked(const_pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" cannot be used for const access + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // use unchecked array access + ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + const_reference get_checked(const_pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" always fails the range check + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // note: at performs range check + ptr = &ptr->at(static_cast(std::stoi(reference_token))); + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + /// split the string input to reference tokens + static std::vector split(std::string reference_string) + { + std::vector result; + + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return result; + } + + // check if nonempty reference string begins with slash + if (reference_string[0] != '/') + { + throw std::domain_error("JSON pointer must be empty or begin with '/'"); + } + + // extract the reference tokens: + // - slash: position of the last read slash (or end of string) + // - start: position after the previous slash + for ( + // search for the first slash after the first character + size_t slash = reference_string.find_first_of("/", 1), + // set the beginning of the first reference token + start = 1; + // we can stop if start == string::npos+1 = 0 + start != 0; + // set the beginning of the next reference token + // (will eventually be 0 if slash == std::string::npos) + start = slash + 1, + // find next slash + slash = reference_string.find_first_of("/", start)) + { + // use the text between the beginning of the reference token + // (start) and the last slash (slash). + auto reference_token = reference_string.substr(start, slash - start); + + // check reference tokens are properly escaped + for (size_t pos = reference_token.find_first_of("~"); + pos != std::string::npos; + pos = reference_token.find_first_of("~", pos + 1)) + { + assert(reference_token[pos] == '~'); + + // ~ must be followed by 0 or 1 + if (pos == reference_token.size() - 1 or + (reference_token[pos + 1] != '0' and + reference_token[pos + 1] != '1')) + { + throw std::domain_error("escape error: '~' must be followed with '0' or '1'"); + } + } + + // finally, store the reference token + unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + private: + /*! + @brief replace all occurrences of a substring by another string + + @param[in,out] s the string to manipulate + @param[in] f the substring to replace with @a t + @param[in] t the string to replace @a f + + @return The string @a s where all occurrences of @a f are replaced + with @a t. + + @pre The search string @a f must not be empty. + + @since version 2.0.0 + */ + static void replace_substring(std::string& s, + const std::string& f, + const std::string& t) + { + assert(not f.empty()); + + for ( + size_t pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t + pos = s.find(f, pos + t.size()) // find next occurrence of f + ); + } + + /// escape tilde and slash + static std::string escape(std::string s) + { + // escape "~"" to "~0" and "/" to "~1" + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; + } + + /// unescape tilde and slash + static void unescape(std::string& s) + { + // first transform any occurrence of the sequence '~1' to '/' + replace_substring(s, "~1", "/"); + // then transform any occurrence of the sequence '~0' to '~' + replace_substring(s, "~0", "~"); + } + + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + static void flatten(const std::string& reference_string, + const basic_json& value, + basic_json& result) + { + switch (value.m_type) + { + case value_t::array: + { + if (value.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + for (size_t i = 0; i < value.m_value.array->size(); ++i) + { + flatten(reference_string + "/" + std::to_string(i), + value.m_value.array->operator[](i), result); + } + } + break; + } + + case value_t::object: + { + if (value.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_value.object) + { + flatten(reference_string + "/" + escape(element.first), + element.second, result); + } + } + break; + } + + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } + } + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + */ + static basic_json unflatten(const basic_json& value) + { + if (not value.is_object()) + { + throw std::domain_error("only objects can be unflattened"); + } + + basic_json result; + + // iterate the JSON object values + for (const auto& element : *value.m_value.object) + { + if (not element.second.is_primitive()) + { + throw std::domain_error("values in object must be primitive"); + } + + // assign value to reference pointed to by JSON pointer; Note + // that if the JSON pointer is "" (i.e., points to the whole + // value), function get_and_create returns a reference to + // result itself. An assignment will then create a primitive + // value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } + + private: + /// the reference tokens + std::vector reference_tokens {}; + }; + + ////////////////////////// + // JSON Pointer support // + ////////////////////////// + + /// @name JSON Pointer functions + /// @{ + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. Similar to @ref operator[](const typename + object_t::key_type&), `null` values are created in arrays and objects if + necessary. + + In particular: + - If the JSON pointer points to an object key that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. + - If the JSON pointer points to an array index that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. All indices between the current maximum and the given + index are also filled with `null`. + - The special value `-` is treated as a synonym for the index past the + end. + + @param[in] ptr a JSON pointer + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,operatorjson_pointer} + + @since version 2.0.0 + */ + reference operator[](const json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. The function does not change the JSON + value; no `null` values are created. In particular, the the special value + `-` yields an exception. + + @param[in] ptr JSON pointer to the desired element + + @return const reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} + + @since version 2.0.0 + */ + const_reference operator[](const json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a reference to the element at with specified JSON pointer @a ptr, + with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,at_json_pointer} + + @since version 2.0.0 + */ + reference at(const json_pointer& ptr) + { + return ptr.get_checked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a const reference to the element at with specified JSON pointer @a + ptr, with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,at_json_pointer_const} + + @since version 2.0.0 + */ + const_reference at(const json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + /*! + @brief return flattened JSON value + + The function creates a JSON object whose keys are JSON pointers (see [RFC + 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all + primitive. The original JSON value can be restored using the @ref + unflatten() function. + + @return an object that maps JSON pointers to primitve values + + @note Empty objects and arrays are flattened to `null` and will not be + reconstructed correctly by the @ref unflatten() function. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a JSON object is flattened to an + object whose keys consist of JSON pointers.,flatten} + + @sa @ref unflatten() for the reverse function + + @since version 2.0.0 + */ + basic_json flatten() const + { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + /*! + @brief unflatten a previously flattened JSON value + + The function restores the arbitrary nesting of a JSON value that has been + flattened before using the @ref flatten() function. The JSON value must + meet certain constraints: + 1. The value must be an object. + 2. The keys must be JSON pointers (see + [RFC 6901](https://tools.ietf.org/html/rfc6901)) + 3. The mapped values must be primitive JSON types. + + @return the original JSON from a flattened version + + @note Empty objects and arrays are flattened by @ref flatten() to `null` + values and can not unflattened to their original type. Apart from + this example, for a JSON value `j`, the following is always true: + `j == j.flatten().unflatten()`. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a flattened JSON object is + unflattened into the original nested JSON object.,unflatten} + + @sa @ref flatten() for the reverse function + + @since version 2.0.0 + */ + basic_json unflatten() const + { + return json_pointer::unflatten(*this); + } + + /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /*! + @brief applies a JSON patch + + [JSON Patch](http://jsonpatch.com) defines a JSON document structure for + expressing a sequence of operations to apply to a JSON) document. With + this funcion, a JSON Patch is applied to the current JSON value by + executing all operations from the patch. + + @param[in] json_patch JSON patch document + @return patched document + + @note The application of a patch is atomic: Either all operations succeed + and the patched document is returned or an exception is thrown. In + any case, the original value is not changed: the patch is applied + to a copy of the value. + + @throw std::out_of_range if a JSON pointer inside the patch could not + be resolved successfully in the current JSON value; example: `"key baz + not found"` + @throw invalid_argument if the JSON patch is malformed (e.g., mandatory + attributes are missing); example: `"operation add must have member path"` + + @complexity Linear in the size of the JSON value and the length of the + JSON patch. As usually only a fraction of the JSON value is affected by + the patch, the complexity can usually be neglected. + + @liveexample{The following code shows how a JSON patch is applied to a + value.,patch} + + @sa @ref diff -- create a JSON patch by comparing two JSON values + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + basic_json patch(const basic_json& json_patch) const + { + // make a working copy to apply the patch to + basic_json result = *this; + + // the valid JSON Patch operations + enum class patch_operations {add, remove, replace, move, copy, test, invalid}; + + const auto get_op = [](const std::string op) + { + if (op == "add") + { + return patch_operations::add; + } + if (op == "remove") + { + return patch_operations::remove; + } + if (op == "replace") + { + return patch_operations::replace; + } + if (op == "move") + { + return patch_operations::move; + } + if (op == "copy") + { + return patch_operations::copy; + } + if (op == "test") + { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, basic_json val) + { + // adding to the root of the target document means replacing it + if (ptr.is_root()) + { + result = val; + } + else + { + // make sure the top element of the pointer exists + json_pointer top_pointer = ptr.top(); + if (top_pointer != ptr) + { + basic_json& x = result.at(top_pointer); + } + + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result[ptr]; + + switch (parent.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = std::stoi(last_path); + if (static_cast(idx) > parent.size()) + { + // avoid undefined behavior + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + else + { + // default case: insert add offset + parent.insert(parent.begin() + static_cast(idx), val); + } + } + break; + } + + default: + { + // if there exists a parent it cannot be primitive + assert(false); // LCOV_EXCL_LINE + } + } + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [&result](json_pointer & ptr) + { + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (it != parent.end()) + { + parent.erase(it); + } + else + { + throw std::out_of_range("key '" + last_path + "' not found"); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(static_cast(std::stoi(last_path))); + } + }; + + // type check + if (not json_patch.is_array()) + { + // a JSON patch must be an array of objects + throw std::invalid_argument("JSON patch must be an array of objects"); + } + + // iterate and apply th eoperations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const std::string & op, + const std::string & member, + bool string_type) -> basic_json& + { + // find value + auto it = val.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; + + // check if desired value is present + if (it == val.m_value.object->end()) + { + throw std::invalid_argument(error_msg + " must have member '" + member + "'"); + } + + // check if result is of type string + if (string_type and not it->second.is_string()) + { + throw std::invalid_argument(error_msg + " must have string member '" + member + "'"); + } + + // no error: return value + return it->second; + }; + + // type check + if (not val.is_object()) + { + throw std::invalid_argument("JSON patch must be an array of objects"); + } + + // collect mandatory members + const std::string op = get_value("op", "op", true); + const std::string path = get_value(op, "path", true); + json_pointer ptr(path); + + switch (get_op(op)) + { + case patch_operations::add: + { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: + { + operation_remove(ptr); + break; + } + + case patch_operations::replace: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const std::string from_path = get_value("move", "from", true); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const std::string from_path = get_value("copy", "from", true);; + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + result[ptr] = result.at(from_ptr); + break; + } + + case patch_operations::test: + { + bool success = false; + try + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + catch (std::out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if test fails + if (not success) + { + throw std::domain_error("unsuccessful: " + val.dump()); + } + + break; + } + + case patch_operations::invalid: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + throw std::invalid_argument("operation value '" + op + "' is invalid"); + } + } + } + + return result; + } + + /*! + @brief creates a diff as a JSON patch + + Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can + be changed into the value @a target by calling @ref patch function. + + @invariant For two JSON values @a source and @a target, the following code + yields always `true`: + @code {.cpp} + source.patch(diff(source, target)) == target; + @endcode + + @note Currently, only `remove`, `add`, and `replace` operations are + generated. + + @param[in] source JSON value to copare from + @param[in] target JSON value to copare against + @param[in] path helper value to create JSON pointers + + @return a JSON patch to convert the @a source to @a target + + @complexity Linear in the lengths of @a source and @a target. + + @liveexample{The following code shows how a JSON patch is created as a + diff for two JSON values.,diff} + + @sa @ref patch -- apply a JSON patch + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + + @since version 2.0.0 + */ + static basic_json diff(const basic_json& source, + const basic_json& target, + std::string path = "") + { + // the patch + basic_json result(value_t::array); + + // if the values are the same, return empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, + {"path", path}, + {"value", target} + }); + } + else + { + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + size_t i = 0; + while (i < source.size() and i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // i now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", path + "/" + std::to_string(i)} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + std::to_string(i)}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.begin(); it != source.end(); ++it) + { + // escape the key name to be used in a JSON patch + const auto key = json_pointer::escape(it.key()); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, + {"path", path + "/" + key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.begin(); it != target.end(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto key = json_pointer::escape(it.key()); + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: + { + // both primitive type: replace value + result.push_back( + { + {"op", "replace"}, + {"path", path}, + {"value", target} + }); + break; + } + } + } + + return result; + } + + /// @} +}; + + +///////////// +// presets // +///////////// + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; +} + + +/////////////////////// +// nonmember support // +/////////////////////// + +// specialization of std::swap, and std::hash +namespace std +{ +/*! +@brief exchanges the values of two JSON objects + +@since version 1.0.0 +*/ +template <> +inline void swap(nlohmann::json& j1, + nlohmann::json& j2) noexcept( + is_nothrow_move_constructible::value and + is_nothrow_move_assignable::value + ) +{ + j1.swap(j2); +} + +/// hash value for JSON objects +template <> +struct hash +{ + /*! + @brief return a hash value for a JSON object + + @since version 1.0.0 + */ + std::size_t operator()(const nlohmann::json& j) const + { + // a naive hashing via the string representation + const auto& h = hash(); + return h(j.dump()); + } +}; +} + +/*! +@brief user-defined string literal for JSON values + +This operator implements a user-defined string literal for JSON objects. It +can be used by adding `"_json"` to a string literal and returns a JSON object +if no parse error occurred. + +@param[in] s a string representation of a JSON object +@return a JSON object + +@since version 1.0.0 +*/ +inline nlohmann::json operator "" _json(const char* s, std::size_t) +{ + return nlohmann::json::parse(reinterpret_cast(s)); +} + +/*! +@brief user-defined string literal for JSON pointer + +This operator implements a user-defined string literal for JSON Pointers. It +can be used by adding `"_json"` to a string literal and returns a JSON pointer +object if no parse error occurred. + +@param[in] s a string representation of a JSON Pointer +@return a JSON pointer object + +@since version 2.0.0 +*/ +inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t) +{ + return nlohmann::json::json_pointer(s); +} + +// restore GCC/clang diagnostic settings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic pop +#endif + +#endif From 42b690daca6404a037e2836ae35b5ba91b2c10b0 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 2 Aug 2016 23:46:12 -0700 Subject: [PATCH 049/723] mrinfo -property all: Improve readability through indentation --- cmd/mrinfo.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/cmd/mrinfo.cpp b/cmd/mrinfo.cpp index 7659fa8942..f2ad7a4f69 100644 --- a/cmd/mrinfo.cpp +++ b/cmd/mrinfo.cpp @@ -133,20 +133,26 @@ void print_strides (const Header& header) std::cout << buffer << "\n"; } -void print_properties (const Header& header, const std::string& key) +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); + print_properties (header, it.first, it.first.size()+2); } } else { const auto values = header.keyval().find (key); - if (values != header.keyval().end()) - std::cout << values->second << "\n"; - else + if (values != header.keyval().end()) { + auto lines = split (values->second, "\n"); + std::cout << lines[0] << "\n"; + for (size_t i = 1; i != lines.size(); ++i) { + lines[i].insert (0, indent, ' '); + std::cout << lines[i] << "\n"; + } + } else { WARN ("no \"" + key + "\" entries found in \"" + header.name() + "\""); + } } } From 35e2243d21127ce84e138d8acdb124d7302664e6 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 2 Aug 2016 23:47:02 -0700 Subject: [PATCH 050/723] transformcalc: Fix command description Leftover description prior to splitting functionalities across transformcalc and transformconvert. --- cmd/transformcalc.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmd/transformcalc.cpp b/cmd/transformcalc.cpp index 2627703f39..41c641c54e 100644 --- a/cmd/transformcalc.cpp +++ b/cmd/transformcalc.cpp @@ -44,10 +44,7 @@ void usage () AUTHOR = "Max Pietsch (maximilian.pietsch@kcl.ac.uk)"; DESCRIPTION - + "This command's function is to process linear transformation matrices." - - + "It allows to perform affine matrix operations or to convert the transformation matrix provided by FSL's flirt command to a format usable in MRtrix" - ; + + "This command's function is to perform calculations on linear transformation matrices."; ARGUMENTS + Argument ("input", "the input for the specified operation").allow_multiple() From af42a6dd9dbcd8ea393d3c26c7ad3e65bc937d0b Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 3 Aug 2016 10:53:14 -0700 Subject: [PATCH 051/723] tcksift and tcksift2: New option -out_mu Allows to extract the (final) value of the SIFT proportionality coefficient for later use. --- cmd/tcksift.cpp | 6 ++++++ cmd/tcksift2.cpp | 6 ++++++ src/dwi/tractography/SIFT/sift.cpp | 3 +++ src/dwi/tractography/SIFT/sifter.h | 1 - 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cmd/tcksift.cpp b/cmd/tcksift.cpp index 6087cc42bb..3cec17e493 100644 --- a/cmd/tcksift.cpp +++ b/cmd/tcksift.cpp @@ -144,6 +144,12 @@ void run () } + opt = get_options ("out_mu"); + if (opt.size()) { + File::OFStream out_mu (opt[0][0]); + out_mu << sifter.mu(); + } + } diff --git a/cmd/tcksift2.cpp b/cmd/tcksift2.cpp index 4b70149ae6..905ccdbcdd 100644 --- a/cmd/tcksift2.cpp +++ b/cmd/tcksift2.cpp @@ -220,6 +220,12 @@ void run () if (output_debug) tckfactor.output_all_debug_images ("after"); + opt = get_options ("out_mu"); + if (opt.size()) { + File::OFStream out_mu (opt[0][0]); + out_mu << tckfactor.mu(); + } + } diff --git a/src/dwi/tractography/SIFT/sift.cpp b/src/dwi/tractography/SIFT/sift.cpp index a1b150307b..ad1e23404c 100644 --- a/src/dwi/tractography/SIFT/sift.cpp +++ b/src/dwi/tractography/SIFT/sift.cpp @@ -51,6 +51,9 @@ const OptionGroup SIFTOutputOption = OptionGroup ("Options to make SIFT provide + Option ("csv", "output statistics of execution per iteration to a .csv file") + Argument ("file").type_file_out() + + Option ("out_mu", "output the final value of SIFT proportionality coefficient mu to a text file") + + Argument ("file").type_file_out() + + Option ("output_debug", "provide various output images for assessing & debugging performace etc."); diff --git a/src/dwi/tractography/SIFT/sifter.h b/src/dwi/tractography/SIFT/sifter.h index e4c36f1412..fdd9ebe205 100644 --- a/src/dwi/tractography/SIFT/sifter.h +++ b/src/dwi/tractography/SIFT/sifter.h @@ -91,7 +91,6 @@ namespace MR using MapType::FOD_sum; using MapType::TD_sum; - using MapType::mu; using MapType::proc_mask; using MapType::num_tracks; From b19fa193d5616f1d17230ab4468d08c09841e0b2 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 3 Aug 2016 15:42:15 -0700 Subject: [PATCH 052/723] mrconvert: Both import and export of JSON files --- cmd/mrconvert.cpp | 58 ++++++++++++++--------- cmd/mrinfo.cpp | 6 +-- docs/reference/commands/mrconvert.rst | 7 ++- docs/reference/commands/mrinfo.rst | 2 +- docs/reference/commands/tcksift.rst | 2 + docs/reference/commands/tcksift2.rst | 2 + docs/reference/commands/transformcalc.rst | 4 +- 7 files changed, 51 insertions(+), 30 deletions(-) diff --git a/cmd/mrconvert.cpp b/cmd/mrconvert.cpp index 558c41445a..b26e3540e5 100644 --- a/cmd/mrconvert.cpp +++ b/cmd/mrconvert.cpp @@ -22,6 +22,7 @@ #include "adapter/extract.h" #include "adapter/permute_axes.h" #include "file/json.h" +#include "file/ofstream.h" #include "dwi/gradient.h" @@ -78,10 +79,14 @@ void usage () "effect for floating-point and binary images.") + Argument ("values").type_sequence_float() - + Option ("json", - "import data from a JSON file into header key-value pairs") + + 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_export", "export data from an image header key-value pairs into a JSON file") + + Argument ("file").type_file_out() + + Stride::Options + DataType::options() @@ -218,7 +223,25 @@ void run () if (get_options ("grad").size() || get_options ("fslgrad").size()) DWI::set_DW_scheme (header_out, DWI::get_DW_scheme (header_in)); - auto opt = get_options ("coord"); + auto opt = get_options ("json_import"); + if (opt.size()) { + std::ifstream in (opt[0][0]); + if (!in) + throw Exception ("Error opening JSON file \"" + std::string(opt[0][0]) + "\""); + nlohmann::json json; + try { + in >> json; + } catch (...) { + throw Exception ("Error parsing JSON file \"" + std::string(opt[0][0]) + "\""); + } + for (auto i = json.cbegin(); i != json.cend(); ++i) { + // Only load simple parameters at the first level + if (i->is_primitive()) + header_out.keyval().insert (std::make_pair (i.key(), str(i.value()))); + } + } + + opt = get_options ("coord"); std::vector> pos; if (opt.size()) { pos.assign (header_in.ndim(), std::vector()); @@ -254,25 +277,6 @@ void run () } - opt = get_options ("json"); - if (opt.size()) { - std::ifstream in (opt[0][0]); - if (!in) - throw Exception ("Error opening JSON file \"" + std::string(opt[0][0]) + "\""); - nlohmann::json json; - try { - in >> json; - } catch (...) { - throw Exception ("Error parsing JSON file \"" + std::string(opt[0][0]) + "\""); - } - for (auto i = json.cbegin(); i != json.cend(); ++i) { - // Only load simple parameters at the first level - if (i->is_primitive()) - header_out.keyval().insert (std::make_pair (i.key(), str(i.value()))); - } - } - - opt = get_options ("scaling"); if (opt.size()) { if (header_out.datatype().is_integer()) { @@ -287,6 +291,16 @@ void run () } + opt = get_options ("json_export"); + if (opt.size()) { + nlohmann::json json; + for (const auto& kv : header_out.keyval()) + json[kv.first] = kv.second; + File::OFStream out (opt[0][0]); + out << json.dump(4); + } + + 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: diff --git a/cmd/mrinfo.cpp b/cmd/mrinfo.cpp index f2ad7a4f69..e54b3e0e27 100644 --- a/cmd/mrinfo.cpp +++ b/cmd/mrinfo.cpp @@ -77,7 +77,7 @@ void usage () "specified key (use 'all' to list all keys found)").allow_multiple() + Argument ("key").type_text() - + Option ("json", "export header key/value entries to a JSON file") + + Option ("json_export", "export header key/value entries to a JSON file") + Argument ("file").type_file_out() + GradImportOptions @@ -170,7 +170,7 @@ void run () if (export_grad && argument.size() > 1) throw Exception ("can only export DW gradient table to file if a single input image is provided"); - std::unique_ptr json (get_options ("json").size() ? new nlohmann::json : nullptr); + std::unique_ptr json (get_options ("json_export").size() ? new nlohmann::json : nullptr); if (get_options ("norealign").size()) Header::do_not_realign_transform = true; @@ -249,7 +249,7 @@ void run () } if (json) { - auto opt = get_options ("json"); + auto opt = get_options ("json_export"); assert (opt.size()); File::OFStream out (opt[0][0]); out << json->dump(4) << "\n"; diff --git a/docs/reference/commands/mrconvert.rst b/docs/reference/commands/mrconvert.rst index a2e9153035..5d45e438b5 100644 --- a/docs/reference/commands/mrconvert.rst +++ b/docs/reference/commands/mrconvert.rst @@ -31,7 +31,12 @@ Options - **-scaling values** specify the data scaling parameters used to rescale the intensity values. These take the form of a comma-separated 2-vector of floating-point values, corresponding to offset & scale, with final intensity values being given by offset + scale * stored_value. By default, the values in the input image header are passed through to the output image header when writing to an integer image, and reset to 0,1 (no scaling) for floating-point and binary images. Note that his option has no effect for floating-point and binary images. -- **-json file** import data from a JSON file into header key-value pairs +Options for handling JSON (JavaScript Object Notation) files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-json_import file** import data from a JSON file into header key-value pairs + +- **-json_export file** export data from an image header key-value pairs into a JSON file Stride options ^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/mrinfo.rst b/docs/reference/commands/mrinfo.rst index aeac0b3769..acb2132a45 100644 --- a/docs/reference/commands/mrinfo.rst +++ b/docs/reference/commands/mrinfo.rst @@ -48,7 +48,7 @@ Options - **-property key** any text properties embedded in the image header under the specified key (use 'all' to list all keys found) -- **-json file** export header key/value entries to a JSON file +- **-json_export file** export header key/value entries to a JSON file DW gradient table import options ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/tcksift.rst b/docs/reference/commands/tcksift.rst index 0ff8e635d9..f1d150f717 100644 --- a/docs/reference/commands/tcksift.rst +++ b/docs/reference/commands/tcksift.rst @@ -51,6 +51,8 @@ Options to make SIFT provide additional output files - **-csv file** output statistics of execution per iteration to a .csv file +- **-out_mu file** output the final value of SIFT proportionality coefficient mu to a text file + - **-output_debug** provide various output images for assessing & debugging performace etc. - **-out_selection path** output a text file containing the binary selection of streamlines diff --git a/docs/reference/commands/tcksift2.rst b/docs/reference/commands/tcksift2.rst index 173b6d6e34..67bd0e8f26 100644 --- a/docs/reference/commands/tcksift2.rst +++ b/docs/reference/commands/tcksift2.rst @@ -47,6 +47,8 @@ Options to make SIFT provide additional output files - **-csv file** output statistics of execution per iteration to a .csv file +- **-out_mu file** output the final value of SIFT proportionality coefficient mu to a text file + - **-output_debug** provide various output images for assessing & debugging performace etc. - **-out_coeffs path** output text file containing the weighting coefficient for each streamline diff --git a/docs/reference/commands/transformcalc.rst b/docs/reference/commands/transformcalc.rst index 349a4104d6..90a131b2d9 100644 --- a/docs/reference/commands/transformcalc.rst +++ b/docs/reference/commands/transformcalc.rst @@ -17,9 +17,7 @@ Synopsis Description ----------- -This command's function is to process linear transformation matrices. - -It allows to perform affine matrix operations or to convert the transformation matrix provided by FSL's flirt command to a format usable in MRtrix +This command's function is to perform calculations on linear transformation matrices. Options ------- From 36b4e1822200b00691de3ce632cba66157579a5e Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 4 Aug 2016 10:19:25 -0700 Subject: [PATCH 053/723] Fix definition of NIfTI::right_left_warning_issued --- lib/file/nifti_utils.cpp | 3 +++ lib/file/nifti_utils.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/file/nifti_utils.cpp b/lib/file/nifti_utils.cpp index f268902333..63b4d460ee 100644 --- a/lib/file/nifti_utils.cpp +++ b/lib/file/nifti_utils.cpp @@ -26,6 +26,9 @@ namespace MR { + bool right_left_warning_issued = false; + + transform_type adjust_transform (const Header& H, std::vector& axes) { diff --git a/lib/file/nifti_utils.h b/lib/file/nifti_utils.h index 976d0b9900..b33fc58e6a 100644 --- a/lib/file/nifti_utils.h +++ b/lib/file/nifti_utils.h @@ -29,7 +29,7 @@ namespace MR namespace NIfTI { - bool right_left_warning_issued = false; + extern bool right_left_warning_issued; transform_type adjust_transform (const Header& H, std::vector& order); From 7690b0ec9e7c06a370a2fe2b44ee3f297f3cddee Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 4 Aug 2016 10:35:51 -0700 Subject: [PATCH 054/723] mrhistmatch: Add reference --- cmd/mrhistmatch.cpp | 6 ++++++ docs/reference/commands/mrhistmatch.rst | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/cmd/mrhistmatch.cpp b/cmd/mrhistmatch.cpp index 1f79db9f69..ec9ff08122 100644 --- a/cmd/mrhistmatch.cpp +++ b/cmd/mrhistmatch.cpp @@ -38,6 +38,12 @@ void usage () { + 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"; + } diff --git a/docs/reference/commands/mrhistmatch.rst b/docs/reference/commands/mrhistmatch.rst index 46ea1abbb3..6c0f65588f 100644 --- a/docs/reference/commands/mrhistmatch.rst +++ b/docs/reference/commands/mrhistmatch.rst @@ -49,6 +49,11 @@ Standard options - **-version** display version information and exit. +References +^^^^^^^^^^ + +* If using inverse contrast normalization for inter-modal (DWI - T1) registration: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 + -------------- From 766d96b86afe1dfa0aee09253093bab17369355d Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 4 Aug 2016 15:00:32 -0700 Subject: [PATCH 055/723] DICOM: Initial extraction of additional useful information Extraction of fields such as phase encode direction / sign, echo time, and effective echo spacing is preliminary, and should not be trusted until it has been tested on a wider array of data. Conversion of this data into the quantity required by FSL eddy is also currently ambiguous and should therefore not be attempted. --- lib/file/dicom/image.cpp | 27 ++++++++++++++++++++++++++- lib/file/dicom/image.h | 8 ++++++++ lib/file/dicom/mapper.cpp | 25 +++++++++++++++++++++---- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/lib/file/dicom/image.cpp b/lib/file/dicom/image.cpp index b39fe08d2c..ce8c9b7ca9 100644 --- a/lib/file/dicom/image.cpp +++ b/lib/file/dicom/image.cpp @@ -80,6 +80,21 @@ namespace MR { G[1] = item.get_float()[1]; G[2] = item.get_float()[2]; return; + case 0x1312U: + if (item.get_string()[0] == "ROW") + pe_axis = 0; + else if (item.get_string()[0] == "COL") + pe_axis = 1; + return; + case 0x0095U: + pixel_bandwidth = item.get_float()[0]; + return; + case 0x0081U: + echo_time = item.get_float()[0]; + return; + case 0x0091: + echo_train_length = item.get_int()[0]; + return; } return; case 0x0020U: @@ -206,6 +221,12 @@ namespace MR { G[2] = item.get_float()[2]; } return; + // Siemens bandwidth per pixel phase encode + // At least, it's what's reported here: http://lcni.uoregon.edu/kb-articles/kb-0003 + // Doesn't appear to work; use CSA field "BandwidthPerPixelPhaseEncode" instead + //case 0x1028U: + // bandwidth_per_pixel_phase_encode = item.get_float()[0]; + // return; } return; case 0x0029U: // Siemens CSA entry @@ -284,6 +305,10 @@ namespace MR { images_in_mosaic = entry.get_int(); else if (strcmp ("SliceNormalVector", entry.key()) == 0) entry.get_float (orientation_z); + else if (strcmp ("PhaseEncodingDirectionPositive", entry.key()) == 0) + pe_sign = (entry.get_int() > 0) ? 1 : -1; + else if (strcmp ("BandwidthPerPixelPhaseEncode", entry.key()) == 0) + bandwidth_per_pixel_phase_encode = entry.get_float(); } if (G[0] && bvalue) @@ -356,7 +381,7 @@ namespace MR { } - std::vector Frame::count (const std::vector& frames) + std::vector Frame::count (const std::vector& frames) { std::vector dim (3, 0); std::vector index (3, 1); diff --git a/lib/file/dicom/image.h b/lib/file/dicom/image.h index 2079fd73ae..1e0011be15 100644 --- a/lib/file/dicom/image.h +++ b/lib/file/dicom/image.h @@ -43,6 +43,10 @@ namespace MR { bvalue = G[0] = G[1] = G[2] = NAN; data = bits_alloc = data_size = frame_offset = 0; DW_scheme_wrt_image = false; + pe_axis = 3; + pe_sign = 0; + pixel_bandwidth = bandwidth_per_pixel_phase_encode = echo_time = NAN; + echo_train_length = 0; } size_t acq_dim[2], dim[2], series_num, instance, acq, sequence; @@ -51,6 +55,10 @@ namespace MR { size_t data, bits_alloc, data_size, frame_offset; std::string filename; bool DW_scheme_wrt_image; + size_t pe_axis; + int pe_sign; + default_type pixel_bandwidth, bandwidth_per_pixel_phase_encode, echo_time; + size_t echo_train_length; std::vector index; bool operator< (const Frame& frame) const { diff --git a/lib/file/dicom/mapper.cpp b/lib/file/dicom/mapper.cpp index 4de7ca00da..647da16854 100644 --- a/lib/file/dicom/mapper.cpp +++ b/lib/file/dicom/mapper.cpp @@ -36,8 +36,8 @@ namespace MR { Patient* patient (series[0]->study->patient); std::string sbuf = ( patient->name.size() ? patient->name : "unnamed" ); sbuf += " " + format_ID (patient->ID); - if (series[0]->modality.size()) sbuf - += std::string (" [") + series[0]->modality + "]"; + if (series[0]->modality.size()) + sbuf += std::string (" [") + series[0]->modality + "]"; if (series[0]->name.size()) sbuf += std::string (" ") + series[0]->name; add_line (H.keyval()["comments"], sbuf); @@ -104,11 +104,28 @@ namespace MR { add_line (H.keyval()["comments"], sbuf); } + const Image& image (*(*series[0])[0]); + if (image.pe_axis != 3 && image.pe_sign) { + std::string pe_symbol; + switch (image.pe_axis) { + case 0: pe_symbol = "i"; break; + case 1: pe_symbol = "j"; break; + case 2: pe_symbol = "k"; break; + default: assert (0); + } + if (image.pe_sign < 0) + pe_symbol += '-'; + add_line (H.keyval()["comments"], "PhaseEncodingDirection: " + pe_symbol); + } + if (std::isfinite (image.bandwidth_per_pixel_phase_encode) && image.pe_axis != 3) { + const default_type effective_echo_spacing = 1.0 / (image.bandwidth_per_pixel_phase_encode * image.dim[image.pe_axis]); + add_line (H.keyval()["comments"], "EffectiveEchoSpacing: " + str (effective_echo_spacing, 3)); + } - - const Image& image (*(*series[0])[0]); + if (std::isfinite (image.echo_time)) + add_line (H.keyval()["comments"], "EchoTime: " + str (0.001 * image.echo_time, 6)); size_t nchannels = image.frames.size() ? 1 : image.data_size / (image.dim[0] * image.dim[1] * (image.bits_alloc/8)); if (nchannels > 1) From da09915047111650027b2a41a15959ac242bc3f8 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 4 Aug 2016 15:07:22 -0700 Subject: [PATCH 056/723] Scripts: New library function getHeaderProperty() Unlike GetHeaderInfo(), this queries the general header key/value pairs rather than expected fields that must be defined in a valid image hedaer. --- scripts/lib/getHeaderProperty.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 scripts/lib/getHeaderProperty.py diff --git a/scripts/lib/getHeaderProperty.py b/scripts/lib/getHeaderProperty.py new file mode 100644 index 0000000000..52f50f60e3 --- /dev/null +++ b/scripts/lib/getHeaderProperty.py @@ -0,0 +1,13 @@ +def getHeaderProperty(image_path, key): + import lib.app, subprocess + from lib.printMessage import printMessage + command = [ 'mrinfo', image_path, '-property', key ] + if lib.app.verbosity > 1: + printMessage('Command: \'' + ' '.join(command) + '\' (piping data to local storage)') + proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=None) + result, err = proc.communicate() + result = result.rstrip().decode('utf-8') + if lib.app.verbosity > 1: + printMessage('Result: ' + result) + return result + From 7e8c008bbb0295d7a258a796abd6e0b2f79498e7 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 10 Aug 2016 17:26:59 +1000 Subject: [PATCH 057/723] DICOM phase encode info changes - Move information relevant to phase-encoding to Header::keyval(), rather than being lines within header "comments'. - Add TotalReadoutTime field. --- lib/file/dicom/mapper.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/file/dicom/mapper.cpp b/lib/file/dicom/mapper.cpp index 647da16854..3efa3ab2ad 100644 --- a/lib/file/dicom/mapper.cpp +++ b/lib/file/dicom/mapper.cpp @@ -116,16 +116,17 @@ namespace MR { } if (image.pe_sign < 0) pe_symbol += '-'; - add_line (H.keyval()["comments"], "PhaseEncodingDirection: " + pe_symbol); + H.keyval()["PhaseEncodingDirection"] = pe_symbol; } if (std::isfinite (image.bandwidth_per_pixel_phase_encode) && image.pe_axis != 3) { const default_type effective_echo_spacing = 1.0 / (image.bandwidth_per_pixel_phase_encode * image.dim[image.pe_axis]); - add_line (H.keyval()["comments"], "EffectiveEchoSpacing: " + str (effective_echo_spacing, 3)); + H.keyval()["EffectiveEchoSpacing"] = str (effective_echo_spacing, 3); + H.keyval()["TotalReadoutTime"] = str (effective_echo_spacing * (image.dim[image.pe_axis] - 1), 3); } if (std::isfinite (image.echo_time)) - add_line (H.keyval()["comments"], "EchoTime: " + str (0.001 * image.echo_time, 6)); + H.keyval()["EchoTime"] = str (0.001 * image.echo_time, 6); size_t nchannels = image.frames.size() ? 1 : image.data_size / (image.dim[0] * image.dim[1] * (image.bits_alloc/8)); if (nchannels > 1) From c15f6527d856460508b92aed1bca810e234a5d1f Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 10 Aug 2016 17:29:02 +1000 Subject: [PATCH 058/723] Initial code commit for phase-encode information handling --- src/dwi/phase_encoding.cpp | 131 +++++++++++++++++++++++ src/dwi/phase_encoding.h | 214 +++++++++++++++++++++++++++++++++++++ 2 files changed, 345 insertions(+) create mode 100644 src/dwi/phase_encoding.cpp create mode 100644 src/dwi/phase_encoding.h diff --git a/src/dwi/phase_encoding.cpp b/src/dwi/phase_encoding.cpp new file mode 100644 index 0000000000..2d666a461a --- /dev/null +++ b/src/dwi/phase_encoding.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "dwi/phase_encoding.h" + +namespace MR +{ + namespace DWI + { + namespace PhaseEncoding + { + + + + std::string dir2id (const Eigen::Vector3& axis) + { + if (axis[0] == -1) { + assert (!axis[1]); assert (!axis[2]); return "i-"; + } else if (axis[0] == 1) { + assert (!axis[1]); assert (!axis[2]); return "i"; + } else if (axis[1] == -1) { + assert (!axis[0]); assert (!axis[2]); return "j-"; + } else if (axis[1] == 1) { + assert (!axis[0]); assert (!axis[2]); return "j"; + } else if (axis[2] == -1) { + assert (!axis[0]); assert (!axis[1]); return "k-"; + } else if (axis[2] == 1) { + assert (!axis[0]); assert (!axis[1]); return "k"; + } else { + throw Exception ("Malformed phase-encode direction: \"" + str(axis.transpose()) + "\""); + } + } + Eigen::Vector3 id2dir (const std::string& id) + { + if (id == "i-") + return { -1, 0, 0 }; + else if (id == "i") + return { 1, 0, 0 }; + else if (id == "j-") + return { 0, -1, 0 }; + else if (id == "j") + return { 0, 1, 0 }; + else if (id == "k-") + return { 0, 0, -1 }; + else if (id == "k") + return { 0, 0, 1 }; + else + throw Exception ("Malformed phase-encode identifier: \"" + id + "\""); + } + + + + Eigen::MatrixXd get_scheme (const Header& header) + { + Eigen::MatrixXd PE; + const auto it = header.keyval().find ("pe_scheme"); + if (it != header.keyval().end()) { + const auto lines = split_lines (it->second); + for (size_t row = 0; row < lines.size(); ++row) { + const auto values = parse_floats (lines[row]); + if (PE.cols() == 0) + PE.resize (lines.size(), values.size()); + else if (PE.cols() != ssize_t (values.size())) + throw Exception ("malformed PE scheme in image \"" + header.name() + "\" - uneven number of entries per row"); + for (size_t col = 0; col < values.size(); ++col) + PE(row, col) = values[col]; + } + } else { + const auto it_dir = header.keyval().find ("PhaseEncodingDirection"); + const auto it_time = header.keyval().find ("TotalReadoutTime"); + if (it_dir != header.keyval().end() && it_time != header.keyval().end()) { + Eigen::Matrix row; + row.head<3>() = id2dir (*it_dir); + row(3) = *it_time; + PE.resize ((header.ndim() > 3) ? header.size(3) : 1, 4); + PE.rowwise() = row; + } + } + return PE; + } + + + + Eigen::MatrixXd eddy2scheme (const Eigen::MatrixXd& config, const Eigen::Array& indices) + { + if (config.cols() != 4) + throw Exception ("Expected 4 columns in EDDY-format phase-encoding config file"); + Eigen::MatrixXd result (indices.size(), 4); + for (size_t row = 0; row != indices.size(); ++row) { + if (indices[row] > config.rows()) + throw Exception ("Malformed EDDY-style phase-encoding information: Index exceeds number of config entries"); + result.row(row) = config.row(indices[row]-1); + } + return result; + } + + + + Eigen::MatrixXd load (const std::string& path) + { + const Eigen::MatrixXd result = load_matrix (path); + check (result); + return result; + } + + Eigen::MatrixXd load_eddy (const std::string& config_path, const std::string& index_path) + { + const Eigen::MatrixXd config = load_matrix (config_path); + const Eigen::Array indices = load_vector (index_path); + return eddy2scheme (config, indices); + } + + + + } + } +} + + diff --git a/src/dwi/phase_encoding.h b/src/dwi/phase_encoding.h new file mode 100644 index 0000000000..772ff3d09c --- /dev/null +++ b/src/dwi/phase_encoding.h @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __dwi_phaseencoding_h__ +#define __dwi_phaseencoding_h__ + + +#include "header.h" +#include "file/ofstream.h" + + +namespace MR +{ + namespace DWI + { + namespace PhaseEncoding + { + + + + //! convert phase encoding direction between formats + /*! these helper functions convert the definition of + * phase-encoding direction between a 3-vector (e.g. + * [0 1 0] ) and a NIfTI axis identifier (e.g. 'i-') + */ + std::string dir2id (const Eigen::Vector3&); + Eigen::Vector3 id2dir (const std::string&); + + + + //! store the phase encoding matrix in a header + /*! this will store the phase encoding matrix into the + * Header::keyval() structure of \a header. + * - If the phase encoding direction and/or total readout + * time varies between volumes, then the information + * will be stored + */ + template + void set_scheme (Header& header, const MatrixType& PE) + { + if (!PE.rows()) { + header.keyval().erase ("pe_scheme"); + header.keyval().erase ("PhaseEncodingDirection"); + header.keyval().erase ("TotalReadoutTime"); + return; + } + check_scheme (header, PE); + std::string pe_scheme; + std::string first_line; + bool variation = false; + for (ssize_t row = 0; row < PE.rows(); ++row) { + std::string line; + for (ssize_t col = 0; col < PE.cols(); ++col) { + line += str(PE(row,col), 3); + if (col < PE.cols() - 1) line += ","; + } + add_line (pe_scheme, line); + if (first_line.empty()) + first_line = line; + else if (line != first_line) + variation = true; + } + if (variation) { + header.keyval()["pe_scheme"] = pe_scheme; + header.keyval().erase ("PhaseEncodingDirection"); + header.keyval().erase ("TotalReadoutTime"); + } else { + header.keyval().erase ("pe_scheme"); + header.keyval()["PhaseEncodingDirection"] = dir2id (PE.block<3, 1>(0, 0)); + if (PE.cols() >= 4) + header.keyval()["TotalReadoutTime"] = PE(0, 3); + else + header.keyval().erase ("TotalReadoutTime"); + } + } + + + + //! parse the phase encoding matrix from a header + /*! extract the phase encoding matrix stored in the \a header if one + * is present. This is expected to be stored in the Header::keyval() + * structure, under the key 'pe_scheme'. + */ + Eigen::MatrixXd get_scheme (const Header& header); + + + + //! check that a phase-encoding table is valid + template + inline void check (const MatrixType& PE) + { + if (!PE.rows()) + throw Exception ("No valid phase encoding table found"); + + if (PE.cols() < 3) + throw Exception ("Phase-encoding matrix must have at least 3 columns"); + + for (size_t row = 0; row != PE.rows(); ++row) { + for (size_t axis = 0; axis != 3; ++axis) { + if (std::round (PE(row, axis)) != PE(row, axis)) + throw Exception ("Phase-encoding matrix contains non-integral axis designation"); + } + } + } + + + + //! check that the PE scheme matches the DWI data in \a header + template + inline void check (const Header& header, const MatrixType& PE) + { + check (PE); + + if (((header.ndim() > 3) ? header.size (3) : 1) != (int) PE.rows()) + throw Exception ("Number of volumes in image \"" + header.name() + "\" does not match that in phase encoding table"); + } + + + + //! Convert a phase-encoding scheme into the EDDY config / indices format + template + void scheme2eddy (const MatrixType& PE, Eigen::MatrixXd& config, Eigen::Array& indices) + { + try { + check_scheme (PE); + } catch (Exception& e) { + throw Exception (e, "Cannot convert phase-encoding scheme to eddy format"); + } + if (PE.cols() != 4) + throw Exception ("Phase-encode matrix requires 4 columns to convert to eddy format"); + config.resize (0, 0); + indices.resize (PE.rows()); + for (size_t PE_row = 0; PE_row != PE.rows(); ++PE_row) { + + for (size_t config_row = 0; config_row != config.rows(); ++config_row) { + if (PE.block<1,3>(PE_row, 0) == config.block<1,3>(config_row, 0) + && ((std::abs(PE(PE_row, 3) - config(config_row, 3))) / (PE(PE_row, 3) + config(config_row, 3)) < 1e-3)) { + + // FSL-style index file indexes from 1 + indices[PE_row] = config_row + 1; + continue; + + } + } + // No corresponding match found in config matrix; create a new entry + config.conservativeResize (config.rows()+1, 4); + config.row(config.rows()-1) = PE.row(PE_row); + indices[PE_row] = config.rows(); + + } + } + + //! Convert phase-encoding informat from the EDDY config / indices format into a standard scheme + Eigen::MatrixXd eddy2scheme (const Eigen::MatrixXd&, const Eigen::Array&); + + + + //! Save a phase-encoding scheme to file + template + void save (const MatrixType& PE, const std::string& path) + { + try { + check_scheme (PE); + } catch (Exception& e) { + throw Exception (e, "Cannot export phase-encoding table to file \"" + path + "\""); + } + + File::OFStream out (path); + for (size_t row = 0; row != PE.rows(); ++row) { + // Write phase-encode direction as integers; other information as floating-point + out << PE.block<1, 3>(row, 0).cast(); + if (PE.cols() > 3) + out << " " << PE.block(row, 0, 1, PE.cols()-3); + out << "\n"; + } + } + + //! Save a phase-encoding scheme to EDDY format config / index files + template + void save_eddy (const MatrixType& PE, const std::string& config_path, const std::string& index_path) + { + Eigen::MatrixXd config; + Eigen::Array indices; + scheme2eddy (PE, config, indices); + save_matrix (config, config_path); + save_vector (indices, index_path); + } + + + + //! Load a phase-encoding scheme from either a matrix file, or an EDDY-format comfig / indices file pair + Eigen::MatrixXd load (const std::string&); + Eigen::MatrixXd load_eddy (const std::string&, const std::string&); + + + + } + } +} + +#endif + From 71db2c160405028946e3d9e1480d978add4f3c9a Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 10 Aug 2016 05:53:08 -0700 Subject: [PATCH 059/723] First compilable version including phase encoding library functionality This included moving the phase encoding headers from src/dwi/ to lib/: This allows the DICOM import code to make use of the set_scheme() function, which should therefore add either PhaseEncodingDirection or pe_scheme to the image header depending on whether or not the phase encode direction remains fixed throughout the image series. --- lib/file/dicom/image.cpp | 26 ++++- lib/file/dicom/image.h | 3 +- lib/file/dicom/mapper.cpp | 23 +--- lib/phase_encoding.cpp | 127 ++++++++++++++++++++++ lib/phase_encoding.h | 214 +++++++++++++++++++++++++++++++++++++ src/dwi/phase_encoding.cpp | 131 ----------------------- src/dwi/phase_encoding.h | 214 ------------------------------------- 7 files changed, 370 insertions(+), 368 deletions(-) create mode 100644 lib/phase_encoding.cpp create mode 100644 lib/phase_encoding.h delete mode 100644 src/dwi/phase_encoding.cpp delete mode 100644 src/dwi/phase_encoding.h diff --git a/lib/file/dicom/image.cpp b/lib/file/dicom/image.cpp index ce8c9b7ca9..4d3847ba63 100644 --- a/lib/file/dicom/image.cpp +++ b/lib/file/dicom/image.cpp @@ -445,7 +445,7 @@ namespace MR { - std::string Frame::get_DW_scheme (const std::vector& frames, size_t nslices, const transform_type& image_transform) + std::string Frame::get_DW_scheme (const std::vector& frames, const size_t nslices, const transform_type& image_transform) { if (!std::isfinite (frames[0]->bvalue)) { DEBUG ("no DW encoding information found in DICOM frames"); @@ -479,6 +479,30 @@ namespace MR { } + + Eigen::MatrixXd Frame::get_PE_scheme (const std::vector& frames, const size_t nslices) + { + const size_t num_volumes = frames.size() / nslices; + Eigen::MatrixXd pe_scheme = Eigen::MatrixXd::Zero (num_volumes, 4); + + for (size_t n = 0; n != num_volumes; ++n) { + const Frame& frame (*frames[n*nslices]); + if (frame.pe_axis == 3 || !frame.pe_sign) { + DEBUG ("no phase-encoding information found in DICOM frames"); + return { }; + } + pe_scheme (n, frame.pe_axis) = frame.pe_sign ? 1 : -1; + if (std::isfinite (frame.bandwidth_per_pixel_phase_encode)) { + const default_type effective_echo_spacing = 1.0 / (frame.bandwidth_per_pixel_phase_encode * frame.dim[frame.pe_axis]); + pe_scheme(n, 3) = effective_echo_spacing * (frame.dim[frame.pe_axis] - 1); + } + } + + return pe_scheme; + } + + + } } } diff --git a/lib/file/dicom/image.h b/lib/file/dicom/image.h index 1e0011be15..ef4375c40b 100644 --- a/lib/file/dicom/image.h +++ b/lib/file/dicom/image.h @@ -99,7 +99,8 @@ namespace MR { static std::vector count (const std::vector& frames); static default_type get_slice_separation (const std::vector& frames, size_t nslices); - static std::string get_DW_scheme (const std::vector& frames, size_t nslices, const transform_type& image_transform); + static std::string get_DW_scheme (const std::vector& frames, const size_t nslices, const transform_type& image_transform); + static Eigen::MatrixXd get_PE_scheme (const std::vector& frames, const size_t nslices); friend std::ostream& operator<< (std::ostream& stream, const Frame& item); }; diff --git a/lib/file/dicom/mapper.cpp b/lib/file/dicom/mapper.cpp index 3efa3ab2ad..7c01b98d4e 100644 --- a/lib/file/dicom/mapper.cpp +++ b/lib/file/dicom/mapper.cpp @@ -14,6 +14,7 @@ */ #include "header.h" +#include "phase_encoding.h" #include "image_io/default.h" #include "image_io/mosaic.h" #include "file/dicom/mapper.h" @@ -106,25 +107,6 @@ namespace MR { const Image& image (*(*series[0])[0]); - if (image.pe_axis != 3 && image.pe_sign) { - std::string pe_symbol; - switch (image.pe_axis) { - case 0: pe_symbol = "i"; break; - case 1: pe_symbol = "j"; break; - case 2: pe_symbol = "k"; break; - default: assert (0); - } - if (image.pe_sign < 0) - pe_symbol += '-'; - H.keyval()["PhaseEncodingDirection"] = pe_symbol; - } - - if (std::isfinite (image.bandwidth_per_pixel_phase_encode) && image.pe_axis != 3) { - const default_type effective_echo_spacing = 1.0 / (image.bandwidth_per_pixel_phase_encode * image.dim[image.pe_axis]); - H.keyval()["EffectiveEchoSpacing"] = str (effective_echo_spacing, 3); - H.keyval()["TotalReadoutTime"] = str (effective_echo_spacing * (image.dim[image.pe_axis] - 1), 3); - } - if (std::isfinite (image.echo_time)) H.keyval()["EchoTime"] = str (0.001 * image.echo_time, 6); @@ -204,8 +186,7 @@ namespace MR { H.keyval()["dw_scheme"] = dw_scheme; } - - + PhaseEncoding::set_scheme (H, Frame::get_PE_scheme (frames, dim[1])); for (size_t n = 1; n < frames.size(); ++n) // check consistency of data scaling: if (frames[n]->scale_intercept != frames[n-1]->scale_intercept || diff --git a/lib/phase_encoding.cpp b/lib/phase_encoding.cpp new file mode 100644 index 0000000000..83b272455a --- /dev/null +++ b/lib/phase_encoding.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "phase_encoding.h" + +namespace MR +{ + namespace PhaseEncoding + { + + + + std::string dir2id (const Eigen::Vector3& axis) + { + if (axis[0] == -1) { + assert (!axis[1]); assert (!axis[2]); return "i-"; + } else if (axis[0] == 1) { + assert (!axis[1]); assert (!axis[2]); return "i"; + } else if (axis[1] == -1) { + assert (!axis[0]); assert (!axis[2]); return "j-"; + } else if (axis[1] == 1) { + assert (!axis[0]); assert (!axis[2]); return "j"; + } else if (axis[2] == -1) { + assert (!axis[0]); assert (!axis[1]); return "k-"; + } else if (axis[2] == 1) { + assert (!axis[0]); assert (!axis[1]); return "k"; + } else { + throw Exception ("Malformed phase-encode direction: \"" + str(axis.transpose()) + "\""); + } + } + Eigen::Vector3 id2dir (const std::string& id) + { + if (id == "i-") + return { -1, 0, 0 }; + else if (id == "i") + return { 1, 0, 0 }; + else if (id == "j-") + return { 0, -1, 0 }; + else if (id == "j") + return { 0, 1, 0 }; + else if (id == "k-") + return { 0, 0, -1 }; + else if (id == "k") + return { 0, 0, 1 }; + else + throw Exception ("Malformed phase-encode identifier: \"" + id + "\""); + } + + + + Eigen::MatrixXd get_scheme (const Header& header) + { + Eigen::MatrixXd PE; + const auto it = header.keyval().find ("pe_scheme"); + if (it != header.keyval().end()) { + const auto lines = split_lines (it->second); + for (size_t row = 0; row < lines.size(); ++row) { + const auto values = parse_floats (lines[row]); + if (PE.cols() == 0) + PE.resize (lines.size(), values.size()); + else if (PE.cols() != ssize_t (values.size())) + throw Exception ("malformed PE scheme in image \"" + header.name() + "\" - uneven number of entries per row"); + for (size_t col = 0; col < values.size(); ++col) + PE(row, col) = values[col]; + } + } else { + const auto it_dir = header.keyval().find ("PhaseEncodingDirection"); + const auto it_time = header.keyval().find ("TotalReadoutTime"); + if (it_dir != header.keyval().end() && it_time != header.keyval().end()) { + Eigen::Matrix row; + row.head<3>() = id2dir (it_dir->second); + row[3] = to(it_time->second); + PE.resize ((header.ndim() > 3) ? header.size(3) : 1, 4); + PE.rowwise() = row.transpose(); + } + } + return PE; + } + + + + Eigen::MatrixXd eddy2scheme (const Eigen::MatrixXd& config, const Eigen::Array& indices) + { + if (config.cols() != 4) + throw Exception ("Expected 4 columns in EDDY-format phase-encoding config file"); + Eigen::MatrixXd result (indices.size(), 4); + for (ssize_t row = 0; row != indices.size(); ++row) { + if (indices[row] > config.rows()) + throw Exception ("Malformed EDDY-style phase-encoding information: Index exceeds number of config entries"); + result.row(row) = config.row(indices[row]-1); + } + return result; + } + + + + Eigen::MatrixXd load (const std::string& path) + { + const Eigen::MatrixXd result = load_matrix (path); + check (result); + return result; + } + + Eigen::MatrixXd load_eddy (const std::string& config_path, const std::string& index_path) + { + const Eigen::MatrixXd config = load_matrix (config_path); + const Eigen::Array indices = load_vector (index_path); + return eddy2scheme (config, indices); + } + + + + } +} + diff --git a/lib/phase_encoding.h b/lib/phase_encoding.h new file mode 100644 index 0000000000..f25dc6ec76 --- /dev/null +++ b/lib/phase_encoding.h @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __phaseencoding_h__ +#define __phaseencoding_h__ + + +#include + +#include "header.h" +#include "file/ofstream.h" + + +namespace MR +{ + namespace PhaseEncoding + { + + + + //! check that a phase-encoding table is valid + template + void check (const MatrixType& PE) + { + if (!PE.rows()) + throw Exception ("No valid phase encoding table found"); + + if (PE.cols() < 3) + throw Exception ("Phase-encoding matrix must have at least 3 columns"); + + for (ssize_t row = 0; row != PE.rows(); ++row) { + for (ssize_t axis = 0; axis != 3; ++axis) { + if (std::round (PE(row, axis)) != PE(row, axis)) + throw Exception ("Phase-encoding matrix contains non-integral axis designation"); + } + } + } + + + + //! check that the PE scheme matches the DWI data in \a header + template + void check (const Header& header, const MatrixType& PE) + { + check (PE); + + if (((header.ndim() > 3) ? header.size (3) : 1) != (int) PE.rows()) + throw Exception ("Number of volumes in image \"" + header.name() + "\" does not match that in phase encoding table"); + } + + + + //! convert phase encoding direction between formats + /*! these helper functions convert the definition of + * phase-encoding direction between a 3-vector (e.g. + * [0 1 0] ) and a NIfTI axis identifier (e.g. 'i-') + */ + std::string dir2id (const Eigen::Vector3&); + Eigen::Vector3 id2dir (const std::string&); + + + + //! store the phase encoding matrix in a header + /*! this will store the phase encoding matrix into the + * Header::keyval() structure of \a header. + * - If the phase encoding direction and/or total readout + * time varies between volumes, then the information + * will be stored + */ + template + void set_scheme (Header& header, const MatrixType& PE) + { + if (!PE.rows()) { + header.keyval().erase ("pe_scheme"); + header.keyval().erase ("PhaseEncodingDirection"); + header.keyval().erase ("TotalReadoutTime"); + return; + } + PhaseEncoding::check (header, PE); + std::string pe_scheme; + std::string first_line; + bool variation = false; + for (ssize_t row = 0; row < PE.rows(); ++row) { + std::string line; + for (ssize_t col = 0; col < PE.cols(); ++col) { + line += str(PE(row,col), 3); + if (col < PE.cols() - 1) line += ","; + } + add_line (pe_scheme, line); + if (first_line.empty()) + first_line = line; + else if (line != first_line) + variation = true; + } + if (variation) { + header.keyval()["pe_scheme"] = pe_scheme; + header.keyval().erase ("PhaseEncodingDirection"); + header.keyval().erase ("TotalReadoutTime"); + } else { + header.keyval().erase ("pe_scheme"); + const Eigen::Vector3 dir { PE(0, 0), PE(0, 1), PE(0, 2) }; + header.keyval()["PhaseEncodingDirection"] = dir2id (dir); + if (PE.cols() >= 4) + header.keyval()["TotalReadoutTime"] = PE(0, 3); + else + header.keyval().erase ("TotalReadoutTime"); + } + } + + + + //! parse the phase encoding matrix from a header + /*! extract the phase encoding matrix stored in the \a header if one + * is present. This is expected to be stored in the Header::keyval() + * structure, under the key 'pe_scheme'. + */ + Eigen::MatrixXd get_scheme (const Header& header); + + + + //! Convert a phase-encoding scheme into the EDDY config / indices format + template + void scheme2eddy (const MatrixType& PE, Eigen::MatrixXd& config, Eigen::Array& indices) + { + try { + check_scheme (PE); + } catch (Exception& e) { + throw Exception (e, "Cannot convert phase-encoding scheme to eddy format"); + } + if (PE.cols() != 4) + throw Exception ("Phase-encode matrix requires 4 columns to convert to eddy format"); + config.resize (0, 0); + indices.resize (PE.rows()); + for (size_t PE_row = 0; PE_row != PE.rows(); ++PE_row) { + + for (size_t config_row = 0; config_row != config.rows(); ++config_row) { + if (PE.template block<1,3>(PE_row, 0).isApprox (config.block<1,3>(config_row, 0)) + && ((std::abs(PE(PE_row, 3) - config(config_row, 3))) / (PE(PE_row, 3) + config(config_row, 3)) < 1e-3)) { + + // FSL-style index file indexes from 1 + indices[PE_row] = config_row + 1; + continue; + + } + } + // No corresponding match found in config matrix; create a new entry + config.conservativeResize (config.rows()+1, 4); + config.row(config.rows()-1) = PE.row(PE_row); + indices[PE_row] = config.rows(); + + } + } + + //! Convert phase-encoding informat from the EDDY config / indices format into a standard scheme + Eigen::MatrixXd eddy2scheme (const Eigen::MatrixXd&, const Eigen::Array&); + + + + //! Save a phase-encoding scheme to file + template + void save (const MatrixType& PE, const std::string& path) + { + try { + check_scheme (PE); + } catch (Exception& e) { + throw Exception (e, "Cannot export phase-encoding table to file \"" + path + "\""); + } + + File::OFStream out (path); + for (size_t row = 0; row != PE.rows(); ++row) { + // Write phase-encode direction as integers; other information as floating-point + out << PE.template block<1, 3>(row, 0).template cast(); + if (PE.cols() > 3) + out << " " << PE.block(row, 0, 1, PE.cols()-3); + out << "\n"; + } + } + + //! Save a phase-encoding scheme to EDDY format config / index files + template + void save_eddy (const MatrixType& PE, const std::string& config_path, const std::string& index_path) + { + Eigen::MatrixXd config; + Eigen::Array indices; + scheme2eddy (PE, config, indices); + save_matrix (config, config_path); + save_vector (indices, index_path); + } + + + + //! Load a phase-encoding scheme from either a matrix file, or an EDDY-format comfig / indices file pair + Eigen::MatrixXd load (const std::string&); + Eigen::MatrixXd load_eddy (const std::string&, const std::string&); + + + + } +} + +#endif + diff --git a/src/dwi/phase_encoding.cpp b/src/dwi/phase_encoding.cpp deleted file mode 100644 index 2d666a461a..0000000000 --- a/src/dwi/phase_encoding.cpp +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - -#include "dwi/phase_encoding.h" - -namespace MR -{ - namespace DWI - { - namespace PhaseEncoding - { - - - - std::string dir2id (const Eigen::Vector3& axis) - { - if (axis[0] == -1) { - assert (!axis[1]); assert (!axis[2]); return "i-"; - } else if (axis[0] == 1) { - assert (!axis[1]); assert (!axis[2]); return "i"; - } else if (axis[1] == -1) { - assert (!axis[0]); assert (!axis[2]); return "j-"; - } else if (axis[1] == 1) { - assert (!axis[0]); assert (!axis[2]); return "j"; - } else if (axis[2] == -1) { - assert (!axis[0]); assert (!axis[1]); return "k-"; - } else if (axis[2] == 1) { - assert (!axis[0]); assert (!axis[1]); return "k"; - } else { - throw Exception ("Malformed phase-encode direction: \"" + str(axis.transpose()) + "\""); - } - } - Eigen::Vector3 id2dir (const std::string& id) - { - if (id == "i-") - return { -1, 0, 0 }; - else if (id == "i") - return { 1, 0, 0 }; - else if (id == "j-") - return { 0, -1, 0 }; - else if (id == "j") - return { 0, 1, 0 }; - else if (id == "k-") - return { 0, 0, -1 }; - else if (id == "k") - return { 0, 0, 1 }; - else - throw Exception ("Malformed phase-encode identifier: \"" + id + "\""); - } - - - - Eigen::MatrixXd get_scheme (const Header& header) - { - Eigen::MatrixXd PE; - const auto it = header.keyval().find ("pe_scheme"); - if (it != header.keyval().end()) { - const auto lines = split_lines (it->second); - for (size_t row = 0; row < lines.size(); ++row) { - const auto values = parse_floats (lines[row]); - if (PE.cols() == 0) - PE.resize (lines.size(), values.size()); - else if (PE.cols() != ssize_t (values.size())) - throw Exception ("malformed PE scheme in image \"" + header.name() + "\" - uneven number of entries per row"); - for (size_t col = 0; col < values.size(); ++col) - PE(row, col) = values[col]; - } - } else { - const auto it_dir = header.keyval().find ("PhaseEncodingDirection"); - const auto it_time = header.keyval().find ("TotalReadoutTime"); - if (it_dir != header.keyval().end() && it_time != header.keyval().end()) { - Eigen::Matrix row; - row.head<3>() = id2dir (*it_dir); - row(3) = *it_time; - PE.resize ((header.ndim() > 3) ? header.size(3) : 1, 4); - PE.rowwise() = row; - } - } - return PE; - } - - - - Eigen::MatrixXd eddy2scheme (const Eigen::MatrixXd& config, const Eigen::Array& indices) - { - if (config.cols() != 4) - throw Exception ("Expected 4 columns in EDDY-format phase-encoding config file"); - Eigen::MatrixXd result (indices.size(), 4); - for (size_t row = 0; row != indices.size(); ++row) { - if (indices[row] > config.rows()) - throw Exception ("Malformed EDDY-style phase-encoding information: Index exceeds number of config entries"); - result.row(row) = config.row(indices[row]-1); - } - return result; - } - - - - Eigen::MatrixXd load (const std::string& path) - { - const Eigen::MatrixXd result = load_matrix (path); - check (result); - return result; - } - - Eigen::MatrixXd load_eddy (const std::string& config_path, const std::string& index_path) - { - const Eigen::MatrixXd config = load_matrix (config_path); - const Eigen::Array indices = load_vector (index_path); - return eddy2scheme (config, indices); - } - - - - } - } -} - - diff --git a/src/dwi/phase_encoding.h b/src/dwi/phase_encoding.h deleted file mode 100644 index 772ff3d09c..0000000000 --- a/src/dwi/phase_encoding.h +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - -#ifndef __dwi_phaseencoding_h__ -#define __dwi_phaseencoding_h__ - - -#include "header.h" -#include "file/ofstream.h" - - -namespace MR -{ - namespace DWI - { - namespace PhaseEncoding - { - - - - //! convert phase encoding direction between formats - /*! these helper functions convert the definition of - * phase-encoding direction between a 3-vector (e.g. - * [0 1 0] ) and a NIfTI axis identifier (e.g. 'i-') - */ - std::string dir2id (const Eigen::Vector3&); - Eigen::Vector3 id2dir (const std::string&); - - - - //! store the phase encoding matrix in a header - /*! this will store the phase encoding matrix into the - * Header::keyval() structure of \a header. - * - If the phase encoding direction and/or total readout - * time varies between volumes, then the information - * will be stored - */ - template - void set_scheme (Header& header, const MatrixType& PE) - { - if (!PE.rows()) { - header.keyval().erase ("pe_scheme"); - header.keyval().erase ("PhaseEncodingDirection"); - header.keyval().erase ("TotalReadoutTime"); - return; - } - check_scheme (header, PE); - std::string pe_scheme; - std::string first_line; - bool variation = false; - for (ssize_t row = 0; row < PE.rows(); ++row) { - std::string line; - for (ssize_t col = 0; col < PE.cols(); ++col) { - line += str(PE(row,col), 3); - if (col < PE.cols() - 1) line += ","; - } - add_line (pe_scheme, line); - if (first_line.empty()) - first_line = line; - else if (line != first_line) - variation = true; - } - if (variation) { - header.keyval()["pe_scheme"] = pe_scheme; - header.keyval().erase ("PhaseEncodingDirection"); - header.keyval().erase ("TotalReadoutTime"); - } else { - header.keyval().erase ("pe_scheme"); - header.keyval()["PhaseEncodingDirection"] = dir2id (PE.block<3, 1>(0, 0)); - if (PE.cols() >= 4) - header.keyval()["TotalReadoutTime"] = PE(0, 3); - else - header.keyval().erase ("TotalReadoutTime"); - } - } - - - - //! parse the phase encoding matrix from a header - /*! extract the phase encoding matrix stored in the \a header if one - * is present. This is expected to be stored in the Header::keyval() - * structure, under the key 'pe_scheme'. - */ - Eigen::MatrixXd get_scheme (const Header& header); - - - - //! check that a phase-encoding table is valid - template - inline void check (const MatrixType& PE) - { - if (!PE.rows()) - throw Exception ("No valid phase encoding table found"); - - if (PE.cols() < 3) - throw Exception ("Phase-encoding matrix must have at least 3 columns"); - - for (size_t row = 0; row != PE.rows(); ++row) { - for (size_t axis = 0; axis != 3; ++axis) { - if (std::round (PE(row, axis)) != PE(row, axis)) - throw Exception ("Phase-encoding matrix contains non-integral axis designation"); - } - } - } - - - - //! check that the PE scheme matches the DWI data in \a header - template - inline void check (const Header& header, const MatrixType& PE) - { - check (PE); - - if (((header.ndim() > 3) ? header.size (3) : 1) != (int) PE.rows()) - throw Exception ("Number of volumes in image \"" + header.name() + "\" does not match that in phase encoding table"); - } - - - - //! Convert a phase-encoding scheme into the EDDY config / indices format - template - void scheme2eddy (const MatrixType& PE, Eigen::MatrixXd& config, Eigen::Array& indices) - { - try { - check_scheme (PE); - } catch (Exception& e) { - throw Exception (e, "Cannot convert phase-encoding scheme to eddy format"); - } - if (PE.cols() != 4) - throw Exception ("Phase-encode matrix requires 4 columns to convert to eddy format"); - config.resize (0, 0); - indices.resize (PE.rows()); - for (size_t PE_row = 0; PE_row != PE.rows(); ++PE_row) { - - for (size_t config_row = 0; config_row != config.rows(); ++config_row) { - if (PE.block<1,3>(PE_row, 0) == config.block<1,3>(config_row, 0) - && ((std::abs(PE(PE_row, 3) - config(config_row, 3))) / (PE(PE_row, 3) + config(config_row, 3)) < 1e-3)) { - - // FSL-style index file indexes from 1 - indices[PE_row] = config_row + 1; - continue; - - } - } - // No corresponding match found in config matrix; create a new entry - config.conservativeResize (config.rows()+1, 4); - config.row(config.rows()-1) = PE.row(PE_row); - indices[PE_row] = config.rows(); - - } - } - - //! Convert phase-encoding informat from the EDDY config / indices format into a standard scheme - Eigen::MatrixXd eddy2scheme (const Eigen::MatrixXd&, const Eigen::Array&); - - - - //! Save a phase-encoding scheme to file - template - void save (const MatrixType& PE, const std::string& path) - { - try { - check_scheme (PE); - } catch (Exception& e) { - throw Exception (e, "Cannot export phase-encoding table to file \"" + path + "\""); - } - - File::OFStream out (path); - for (size_t row = 0; row != PE.rows(); ++row) { - // Write phase-encode direction as integers; other information as floating-point - out << PE.block<1, 3>(row, 0).cast(); - if (PE.cols() > 3) - out << " " << PE.block(row, 0, 1, PE.cols()-3); - out << "\n"; - } - } - - //! Save a phase-encoding scheme to EDDY format config / index files - template - void save_eddy (const MatrixType& PE, const std::string& config_path, const std::string& index_path) - { - Eigen::MatrixXd config; - Eigen::Array indices; - scheme2eddy (PE, config, indices); - save_matrix (config, config_path); - save_vector (indices, index_path); - } - - - - //! Load a phase-encoding scheme from either a matrix file, or an EDDY-format comfig / indices file pair - Eigen::MatrixXd load (const std::string&); - Eigen::MatrixXd load_eddy (const std::string&, const std::string&); - - - - } - } -} - -#endif - From 9e985ac690116b9868be01cb8f00fa358770503c Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 10 Aug 2016 06:55:18 -0700 Subject: [PATCH 060/723] Phase encoding header data: Basic command handling Propagate information in dwiextract, mrcat and mrconvert commands --- cmd/dwiextract.cpp | 14 +++++++++-- cmd/mrcat.cpp | 54 +++++++++++++++++++++++++++++++++++------- cmd/mrconvert.cpp | 35 +++++++++++++++++++-------- lib/phase_encoding.cpp | 2 ++ 4 files changed, 85 insertions(+), 20 deletions(-) diff --git a/cmd/dwiextract.cpp b/cmd/dwiextract.cpp index 519ccd03c8..bf18e24d82 100644 --- a/cmd/dwiextract.cpp +++ b/cmd/dwiextract.cpp @@ -15,8 +15,9 @@ #include "command.h" -#include "progressbar.h" #include "image.h" +#include "phase_encoding.h" +#include "progressbar.h" #include "dwi/gradient.h" #include "algo/loop.h" #include "adapter/extract.h" @@ -49,7 +50,8 @@ void usage () void run() { - auto input_image = Image::open (argument[0]); + auto input_header = Header::open (argument[0]); + auto input_image = input_header.get_image(); Eigen::MatrixXd grad = DWI::get_valid_DW_scheme (input_image); @@ -91,6 +93,14 @@ void run() new_grad.row (i) = grad.row (volumes[i]); DWI::set_DW_scheme (header, new_grad); + const auto pe_scheme = PhaseEncoding::get_scheme (input_header); + if (pe_scheme.rows()) { + 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); + } + auto output_image = Image::create (argument[1], header); auto input_volumes = Adapter::make (input_image, 3, volumes); diff --git a/cmd/mrcat.cpp b/cmd/mrcat.cpp index a3d3d10d8a..01dc83f281 100644 --- a/cmd/mrcat.cpp +++ b/cmd/mrcat.cpp @@ -17,6 +17,7 @@ #include "command.h" #include "image.h" #include "algo/loop.h" +#include "phase_encoding.h" #include "progressbar.h" #include "dwi/gradient.h" @@ -113,28 +114,65 @@ void run () { - if (axis > 2) { // concatenate DW schemes - size_t nrows = 0; + if (axis > 2) { + // concatenate DW schemes + ssize_t nrows = 0, ncols = 0; std::vector input_grads; for (int n = 0; n < num_images; ++n) { auto grad = DWI::get_DW_scheme (in[n]); - input_grads.push_back (grad); if (grad.rows() == 0 || grad.cols() < 4) { nrows = 0; break; - } + } + if (!ncols) { + ncols = grad.cols(); + } else if (grad.cols() != ncols) { + nrows = 0; + break; + } nrows += grad.rows(); + input_grads.push_back (std::move (grad)); } - if (nrows) { Eigen::MatrixXd grad_out (nrows, 4); int row = 0; - for (int n = 0; n < num_images; ++n) + for (int n = 0; n < num_images; ++n) { for (ssize_t i = 0; i < input_grads[n].rows(); ++i, ++row) - for (size_t j = 0; j < 4; ++j) - grad_out (row,j) = input_grads[n](i,j); + grad_out.row(row) = input_grads[n].row(i); + } DWI::set_DW_scheme (header_out, grad_out); + } else { + header_out.keyval().erase ("dw_scheme"); + } + + // concatenate PE schemes + nrows = 0; ncols = 0; + std::vector input_schemes; + for (int n = 0; n != num_images; ++n) { + auto scheme = PhaseEncoding::get_scheme (in[n]); + if (!scheme.rows()) { + nrows = 0; + break; + } + if (!ncols) { + ncols = scheme.cols(); + } else if (scheme.cols() != ncols) { + nrows = 0; + break; + } + nrows += scheme.rows(); + input_schemes.push_back (std::move (scheme)); + } + Eigen::MatrixXd scheme_out; + if (nrows) { + scheme_out.resize (nrows, ncols); + size_t row = 0; + for (int n = 0; n != num_images; ++n) { + for (ssize_t i = 0; i != input_grads[n].rows(); ++i, ++row) + scheme_out.row(row) = input_grads[n].row(i); + } } + PhaseEncoding::set_scheme (header_out, scheme_out); } diff --git a/cmd/mrconvert.cpp b/cmd/mrconvert.cpp index b26e3540e5..c09b4ba9a2 100644 --- a/cmd/mrconvert.cpp +++ b/cmd/mrconvert.cpp @@ -17,6 +17,7 @@ #include "command.h" #include "header.h" #include "image.h" +#include "phase_encoding.h" #include "transform.h" #include "algo/threaded_copy.h" #include "adapter/extract.h" @@ -252,17 +253,31 @@ void run () 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); - auto grad = DWI::get_DW_scheme (header_out); - if (axis == 3 && 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"); - header_out.keyval().erase ("dw_scheme"); + if (axis == 3) { + const auto grad = DWI::get_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"); + header_out.keyval().erase ("dw_scheme"); + } + else { + 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); + } } - else { - 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); + Eigen::MatrixXd pe_scheme; + try { + pe_scheme = PhaseEncoding::get_scheme (header_out); + } catch (...) { + WARN ("Phase encoding scheme of input file does not match number of image volumes; omitting information from output image"); + } + if (pe_scheme.rows()) { + 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); } } } diff --git a/lib/phase_encoding.cpp b/lib/phase_encoding.cpp index 83b272455a..491249a502 100644 --- a/lib/phase_encoding.cpp +++ b/lib/phase_encoding.cpp @@ -66,6 +66,8 @@ namespace MR const auto it = header.keyval().find ("pe_scheme"); if (it != header.keyval().end()) { const auto lines = split_lines (it->second); + if (ssize_t(lines.size()) != ((header.ndim() > 3) ? header.size(3) : 1)) + throw Exception ("malformed PE scheme in image \"" + header.name() + "\" - number of rows does not equal number of volumes"); for (size_t row = 0; row < lines.size(); ++row) { const auto values = parse_floats (lines[row]); if (PE.cols() == 0) From 0276fea1d37d80cea03f43a46e99000a941ad51a Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 12 Aug 2016 15:29:58 +1000 Subject: [PATCH 061/723] Further additions to phase-encoding data handling - mrconvert will retain dw_scheme entries beyond the 4th column when using the -axes option - mrconvert will appropriately permute the phase-encoding scheme when using the -axes option - mrconvert has the ability to import and export phase-encoding information from/to external files based on command-line options --- cmd/dwiextract.cpp | 1 + cmd/mrcat.cpp | 2 +- cmd/mrconvert.cpp | 37 +++++++++++-- docs/reference/commands/dwiextract.rst | 7 +++ docs/reference/commands/mrconvert.rst | 14 +++++ lib/phase_encoding.cpp | 76 +++++++++++++++++++++++++- lib/phase_encoding.h | 38 ++++++++++--- 7 files changed, 161 insertions(+), 14 deletions(-) diff --git a/cmd/dwiextract.cpp b/cmd/dwiextract.cpp index bf18e24d82..fbc52adeff 100644 --- a/cmd/dwiextract.cpp +++ b/cmd/dwiextract.cpp @@ -45,6 +45,7 @@ void usage () + Option ("bzero", "output b=0 volumes instead of the diffusion weighted volumes.") + DWI::GradImportOptions() + DWI::ShellOption + + PhaseEncoding::ImportOptions + Stride::Options; } diff --git a/cmd/mrcat.cpp b/cmd/mrcat.cpp index 01dc83f281..b28d0c7484 100644 --- a/cmd/mrcat.cpp +++ b/cmd/mrcat.cpp @@ -149,7 +149,7 @@ void run () { nrows = 0; ncols = 0; std::vector input_schemes; for (int n = 0; n != num_images; ++n) { - auto scheme = PhaseEncoding::get_scheme (in[n]); + auto scheme = PhaseEncoding::parse_scheme (in[n]); if (!scheme.rows()) { nrows = 0; break; diff --git a/cmd/mrconvert.cpp b/cmd/mrconvert.cpp index c09b4ba9a2..ff5b39287c 100644 --- a/cmd/mrconvert.cpp +++ b/cmd/mrconvert.cpp @@ -93,7 +93,10 @@ void usage () + DataType::options() + DWI::GradImportOptions (false) - + DWI::GradExportOptions(); + + DWI::GradExportOptions() + + + PhaseEncoding::ImportOptions + + PhaseEncoding::ExportOptions; } @@ -111,8 +114,8 @@ void permute_DW_scheme (Header& H, const std::vector& axes) permute(axes[axis], axis) = 1.0; const Eigen::Matrix3d R = T.scanner2voxel.rotation() * permute * T.voxel2scanner.rotation(); - Eigen::MatrixXd out (in.rows(), 4); - out.col(3) = in.col(3); // Copy b-values + 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; @@ -121,6 +124,26 @@ void permute_DW_scheme (Header& H, const std::vector& axes) +void permute_PE_scheme (Header& H, const std::vector& axes) +{ + auto in = PhaseEncoding::parse_scheme (H); + if (!in.rows()) + return; + + Eigen::Matrix3d permute = Eigen::Matrix3d::Zero(); + 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) + for (int row = 0; row != in.rows(); ++row) + out.block<1,3>(row, 0) = in.block<1,3>(row, 0) * permute; + + PhaseEncoding::set_scheme (H, out); +} + + + template inline std::vector set_header (Header& header, const ImageType& input) @@ -144,6 +167,7 @@ inline std::vector set_header (Header& header, const ImageType& input) header.size(i) = axes[i] < 0 ? 1 : input.size (axes[i]); } permute_DW_scheme (header, axes); + permute_PE_scheme (header, axes); } else { header.ndim() = input.ndim(); axes.assign (input.ndim(), 0); @@ -184,6 +208,7 @@ inline void copy_permute (Header& header_in, Header& header_out, const std::vect auto out = Header::create (output_filename, header_out).get_image(); 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); @@ -194,6 +219,7 @@ inline void copy_permute (Header& header_in, Header& header_out, const std::vect const auto axes = set_header (header_out, extract); auto out = Image::create (output_filename, header_out); DWI::export_grad_commandline (out); + PhaseEncoding::export_commandline (out); auto perm = Adapter::make (extract, axes); threaded_copy_with_progress (perm, out, 0, std::numeric_limits::max(), 2); @@ -224,6 +250,9 @@ void run () if (get_options ("grad").size() || get_options ("fslgrad").size()) DWI::set_DW_scheme (header_out, DWI::get_DW_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"); if (opt.size()) { std::ifstream in (opt[0][0]); @@ -269,7 +298,7 @@ void run () } Eigen::MatrixXd pe_scheme; try { - pe_scheme = PhaseEncoding::get_scheme (header_out); + pe_scheme = PhaseEncoding::parse_scheme (header_out); } catch (...) { WARN ("Phase encoding scheme of input file does not match number of image volumes; omitting information from output image"); } diff --git a/docs/reference/commands/dwiextract.rst b/docs/reference/commands/dwiextract.rst index ba96d05a39..c9dcdded0c 100644 --- a/docs/reference/commands/dwiextract.rst +++ b/docs/reference/commands/dwiextract.rst @@ -37,6 +37,13 @@ DW Shell selection options - **-shell list** specify one or more diffusion-weighted gradient shells to use during processing, as a comma-separated list of the desired approximate b-values. Note that some commands are incompatible with multiple shells, and will throw an error if more than one b-value is provided. +Options for importing phase-encode tables +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-import_pe_table file** import a phase-encoding table from file + +- **-import_pe_eddy config indices** import phase-encoding information from an EDDY-style config / index file pair + Stride options ^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/mrconvert.rst b/docs/reference/commands/mrconvert.rst index 5d45e438b5..ca3699d188 100644 --- a/docs/reference/commands/mrconvert.rst +++ b/docs/reference/commands/mrconvert.rst @@ -62,6 +62,20 @@ DW gradient table export options - **-export_grad_fsl bvecs_path bvals_path** export the diffusion-weighted gradient table to files in FSL (bvecs / bvals) format +Options for importing phase-encode tables +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-import_pe_table file** import a phase-encoding table from file + +- **-import_pe_eddy config indices** import phase-encoding information from an EDDY-style config / index file pair + +Options for exporting phase-encode tables +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-export_pe_table file** export phase-encoding table to file + +- **-export_pe_eddy config indices** export phase-encoding information to an EDDY-style config / index file pair + Standard options ^^^^^^^^^^^^^^^^ diff --git a/lib/phase_encoding.cpp b/lib/phase_encoding.cpp index 491249a502..bc713755de 100644 --- a/lib/phase_encoding.cpp +++ b/lib/phase_encoding.cpp @@ -22,6 +22,23 @@ namespace MR + using namespace App; + const OptionGroup ImportOptions = OptionGroup ("Options for importing phase-encode tables") + + Option ("import_pe_table", "import a phase-encoding table from file") + + Argument ("file").type_file_in() + + Option ("import_pe_eddy", "import phase-encoding information from an EDDY-style config / index file pair") + + Argument ("config").type_file_in() + + Argument ("indices").type_file_in(); + + const OptionGroup ExportOptions = OptionGroup ("Options for exporting phase-encode tables") + + Option ("export_pe_table", "export phase-encoding table to file") + + Argument ("file").type_file_out() + + Option ("export_pe_eddy", "export phase-encoding information to an EDDY-style config / index file pair") + + Argument ("config").type_file_out() + + Argument ("indices").type_file_out(); + + + std::string dir2id (const Eigen::Vector3& axis) { if (axis[0] == -1) { @@ -60,7 +77,7 @@ namespace MR - Eigen::MatrixXd get_scheme (const Header& header) + Eigen::MatrixXd parse_scheme (const Header& header) { Eigen::MatrixXd PE; const auto it = header.keyval().find ("pe_scheme"); @@ -93,6 +110,42 @@ namespace MR + Eigen::MatrixXd get_scheme (const Header& header) + { + DEBUG ("searching for suitable phase encoding data..."); + using namespace App; + Eigen::MatrixXd result; + + try { + const auto opt_table = get_options ("import_pe_table"); + if (opt_table.size()) + result = load (opt_table[0][0]); + const auto opt_eddy = get_options ("import_pe_eddy"); + if (opt_eddy.size()) { + if (opt_table.size()) + throw Exception ("Please provide phase encoding table using either -import_pe_table or -import_pe_eddy option (not both)"); + result = load_eddy (opt_eddy[0][0], opt_eddy[0][1]); + } + if (!opt_table.size() && !opt_eddy.size()) + result = parse_scheme (header); + } + catch (Exception& e) { + throw Exception (e, "error importing phase encoding table for image \"" + header.name() + "\""); + } + + if (!result.rows()) + return result; + + if (result.cols() < 4) + throw Exception ("unexpected phase encoding table matrix dimensions"); + + INFO ("found " + str (result.rows()) + "x" + str (result.cols()) + " phase encoding table"); + + return result; + } + + + Eigen::MatrixXd eddy2scheme (const Eigen::MatrixXd& config, const Eigen::Array& indices) { if (config.cols() != 4) @@ -108,6 +161,27 @@ namespace MR + void export_commandline (const Header& header) + { + auto check = [&](const Eigen::MatrixXd& m) -> const Eigen::MatrixXd& { + if (!m.rows()) + throw Exception ("no phase-encoding information found within image \"" + header.name() + "\""); + return m; + }; + + auto scheme = parse_scheme (header); + + auto opt = get_options ("export_pe_table"); + if (opt.size()) + save (check (scheme), opt[0][0]); + + opt = get_options ("export_pe_eddy"); + if (opt.size()) + save_eddy (check (scheme), opt[0][0], opt[0][1]); + } + + + Eigen::MatrixXd load (const std::string& path) { const Eigen::MatrixXd result = load_matrix (path); diff --git a/lib/phase_encoding.h b/lib/phase_encoding.h index f25dc6ec76..a1e0633922 100644 --- a/lib/phase_encoding.h +++ b/lib/phase_encoding.h @@ -19,6 +19,7 @@ #include +#include "app.h" #include "header.h" #include "file/ofstream.h" @@ -30,6 +31,11 @@ namespace MR + extern const App::OptionGroup ImportOptions; + extern const App::OptionGroup ExportOptions; + + + //! check that a phase-encoding table is valid template void check (const MatrixType& PE) @@ -124,9 +130,20 @@ namespace MR //! parse the phase encoding matrix from a header /*! extract the phase encoding matrix stored in the \a header if one * is present. This is expected to be stored in the Header::keyval() - * structure, under the key 'pe_scheme'. + * structure, under the key 'pe_scheme'. Alternatively, if the phase + * encoding direction and bandwidth is fixed for all volumes in the + * series, this information may be stored using the keys + * 'PhaseEncodingDirection' and 'TotalReadoutTime'. */ - Eigen::MatrixXd get_scheme (const Header& header); + Eigen::MatrixXd parse_scheme (const Header&); + + + + //! get a phase encoding matrix + /*! get a valid phase-encoding matrix, either from files specified at + * the command-line, or from the contents of the image header. + */ + Eigen::MatrixXd get_scheme (const Header&); @@ -135,17 +152,17 @@ namespace MR void scheme2eddy (const MatrixType& PE, Eigen::MatrixXd& config, Eigen::Array& indices) { try { - check_scheme (PE); + check (PE); } catch (Exception& e) { throw Exception (e, "Cannot convert phase-encoding scheme to eddy format"); } if (PE.cols() != 4) - throw Exception ("Phase-encode matrix requires 4 columns to convert to eddy format"); + throw Exception ("Phase-encoding matrix requires 4 columns to convert to eddy format"); config.resize (0, 0); indices.resize (PE.rows()); - for (size_t PE_row = 0; PE_row != PE.rows(); ++PE_row) { + for (ssize_t PE_row = 0; PE_row != PE.rows(); ++PE_row) { - for (size_t config_row = 0; config_row != config.rows(); ++config_row) { + for (ssize_t config_row = 0; config_row != config.rows(); ++config_row) { if (PE.template block<1,3>(PE_row, 0).isApprox (config.block<1,3>(config_row, 0)) && ((std::abs(PE(PE_row, 3) - config(config_row, 3))) / (PE(PE_row, 3) + config(config_row, 3)) < 1e-3)) { @@ -173,13 +190,13 @@ namespace MR void save (const MatrixType& PE, const std::string& path) { try { - check_scheme (PE); + check (PE); } catch (Exception& e) { throw Exception (e, "Cannot export phase-encoding table to file \"" + path + "\""); } File::OFStream out (path); - for (size_t row = 0; row != PE.rows(); ++row) { + for (ssize_t row = 0; row != PE.rows(); ++row) { // Write phase-encode direction as integers; other information as floating-point out << PE.template block<1, 3>(row, 0).template cast(); if (PE.cols() > 3) @@ -201,6 +218,11 @@ namespace MR + //! Save the phase-encoding scheme from a header to file depending on command-line options + void export_commandline (const Header&); + + + //! Load a phase-encoding scheme from either a matrix file, or an EDDY-format comfig / indices file pair Eigen::MatrixXd load (const std::string&); Eigen::MatrixXd load_eddy (const std::string&, const std::string&); From 2dacd0b2c6526721be63fb5bc9d646573c18df7b Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 12 Aug 2016 16:43:47 +1000 Subject: [PATCH 062/723] mrconvert: New options for manipulating header key/value entries Options -header_cat, -header_erase and -header_set can be used to concatenate, erase and set header key/value entries respectively. This may be useful for those who don't like converting to .mih in order to manipulate these entries. --- cmd/mrconvert.cpp | 39 ++++++++++++++++++++++++--- docs/reference/commands/mrconvert.rst | 9 +++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/cmd/mrconvert.cpp b/cmd/mrconvert.cpp index ff5b39287c..78ae4e0689 100644 --- a/cmd/mrconvert.cpp +++ b/cmd/mrconvert.cpp @@ -81,12 +81,20 @@ void usage () + Argument ("values").type_sequence_float() + 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() - + + 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() + + Argument ("file").type_file_out() + + + OptionGroup ("Options for manipulating image header key/value entries") + + Option ("header_cat", "concatenate a header key/value entry with the specified text").allow_multiple() + + Argument ("key").type_text() + + Argument ("value").type_text() + + Option ("header_erase", "erase a header key/value entry").allow_multiple() + + Argument ("key").type_text() + + Option ("header_set", "set a header key/value entry to the specified text (will erase any existing entry)").allow_multiple() + + Argument ("key").type_text() + + Argument ("value").type_text() + Stride::Options @@ -271,6 +279,29 @@ void run () } } + { + opt = get_options ("header_cat"); + for (size_t i = 0; i != opt.size(); ++i) { + auto entry = header_out.keyval().find (opt[i][0]); + if (entry == header_out.keyval().end()) + header_out.keyval()[opt[i][0]] = std::string(opt[i][1]); + else + add_line (header_out.keyval()[opt[i][0]], std::string(opt[i][1])); + } + opt = get_options ("header_erase"); + for (size_t i = 0; i != opt.size(); ++i) { + auto entry = header_out.keyval().find (opt[i][0]); + if (entry == header_out.keyval().end()) { + WARN ("No header key/value entry \"" + opt[i][0] + "\" found; ignored"); + } else { + header_out.keyval().erase (entry); + } + } + opt = get_options ("header_set"); + for (size_t i = 0; i != opt.size(); ++i) + header_out.keyval()[opt[i][0]] = std::string(opt[i][1]); + } + opt = get_options ("coord"); std::vector> pos; if (opt.size()) { diff --git a/docs/reference/commands/mrconvert.rst b/docs/reference/commands/mrconvert.rst index ca3699d188..ceed13fffa 100644 --- a/docs/reference/commands/mrconvert.rst +++ b/docs/reference/commands/mrconvert.rst @@ -38,6 +38,15 @@ Options for handling JSON (JavaScript Object Notation) files - **-json_export file** export data from an image header key-value pairs into a JSON file +Options for manipulating image header key/value entries +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-header_cat key value** concatenate a header key/value entry with the specified text + +- **-header_erase key** erase a header key/value entry + +- **-header_set key value** set a header key/value entry to the specified text (will erase any existing entry) + Stride options ^^^^^^^^^^^^^^ From a0c6df17fc21133705390e9035a3a7ef3c208066 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 12 Aug 2016 16:46:02 +1000 Subject: [PATCH 063/723] Scripts: Reinstate __print_usage_markdown__ functionality This should be useful for Bids-Apps projects, since we can provide the markdown-formatted usage in the repository README.md file. --- lib/app.cpp | 2 +- scripts/lib/app.py | 4 +++ scripts/lib/cmdlineParser.py | 64 ++++++++++++++++++++++++++++++++++++ scripts/lib/getPEDir.py | 14 +++++++- 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/lib/app.cpp b/lib/app.cpp index 80391e6840..9611dea6bc 100644 --- a/lib/app.cpp +++ b/lib/app.cpp @@ -654,7 +654,7 @@ namespace MR s += format_option (__standard_options[i]); if (REFERENCES.size()) { - s += std::string ("#### References\n\n"); + s += std::string ("## References\n\n"); for (size_t i = 0; i < REFERENCES.size(); ++i) s += indent_newlines (REFERENCES[i]) + "\n\n"; } diff --git a/scripts/lib/app.py b/scripts/lib/app.py index 6f342f0f36..d9c9cb3487 100644 --- a/scripts/lib/app.py +++ b/scripts/lib/app.py @@ -55,6 +55,10 @@ def initialise(): parser.print_help() sys.exit(0) + if sys.argv[-1] == '__print_usage_markdown__': + parser.printUsageMarkdown() + exit(0) + if sys.argv[-1] == '__print_usage_rst__': parser.printUsageRst() exit(0) diff --git a/scripts/lib/cmdlineParser.py b/scripts/lib/cmdlineParser.py index 61cfaf59a8..cb006678d4 100644 --- a/scripts/lib/cmdlineParser.py +++ b/scripts/lib/cmdlineParser.py @@ -198,6 +198,70 @@ def underline(text): + def printUsageMarkdown(self): + import sys + import lib.app + if self._subparsers and len(sys.argv) == 3: + for alg in self._subparsers._group_actions[0].choices: + if alg == sys.argv[1]: + self._subparsers._group_actions[0].choices[alg].printUsageMarkdown() + return + self.error('Invalid subparser nominated') + print ('## Synopsis') + print ('') + print (' ' + self.format_usage()) + print ('') + if self._subparsers: + print ('- *' + self._subparsers._group_actions[0].dest + '*: ' + self._subparsers._group_actions[0].help) + for arg in self._positionals._group_actions: + if arg.metavar: + name = arg.metavar + else: + name = arg.dest + print ('- *' + name + '*: ' + arg.help) + print ('') + print ('## Description') + print ('') + print (self.description) + print ('') + print ('## Options') + print ('') + for group in reversed(self._action_groups): + if group._group_actions and not (len(group._group_actions) == 1 and isinstance(group._group_actions[0], argparse._SubParsersAction)) and not group == self._positionals: + print ('#### ' + group.title) + print ('') + for option in group._group_actions: + text = '/'.join(option.option_strings) + if option.metavar: + text += ' ' + if isinstance(option.metavar, tuple): + text += ' '.join(option.metavar) + else: + text += option.metavar + print ('+ **-' + text + '**
' + option.help) + print ('') + if lib.app.citationList: + print ('## References') + print ('') + for ref in lib.app.citationList: + text = '' + if ref[0]: + text += ref[0] + ': ' + text += ref[1] + print (text) + print ('') + print ('---') + print ('') + print ('**Author:** ' + lib.app.author) + print ('') + print ('**Copyright:** ' + lib.app.copyright) + print ('') + if self._subparsers: + for alg in getAlgorithmList(): + proc = subprocess.call ([ self.prog, alg, '__print_usage_markdown__' ]) + + + def printUsageRst(self): import subprocess, sys import lib.app diff --git a/scripts/lib/getPEDir.py b/scripts/lib/getPEDir.py index 6824376c02..82c2237d34 100644 --- a/scripts/lib/getPEDir.py +++ b/scripts/lib/getPEDir.py @@ -20,6 +20,18 @@ def getPEDir(string): return ( 2, False ) elif string == 'si': return ( 2, True ) + elif string == 'i': + return ( 0, False ) + elif string == 'i-': + return ( 0, True ) + elif string == 'j': + return ( 1, False ) + elif string == 'j-': + return ( 1, True ) + elif string == 'k': + return ( 2, False ) + elif string == 'k-': + return ( 2, True ) else: - errorMessage('Unrecognized phase encode direction specifier') + errorMessage('Unrecognized phase encode direction specifier: ' + string) From f40f31f0939c7d3466d2c34b52e4a9cb79df2822 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 12 Aug 2016 16:54:23 +1000 Subject: [PATCH 064/723] DICOM Phase-encoding import: LPS to RAS --- lib/file/dicom/image.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/file/dicom/image.cpp b/lib/file/dicom/image.cpp index 4d3847ba63..6fadbb4729 100644 --- a/lib/file/dicom/image.cpp +++ b/lib/file/dicom/image.cpp @@ -491,7 +491,11 @@ namespace MR { DEBUG ("no phase-encoding information found in DICOM frames"); return { }; } - pe_scheme (n, frame.pe_axis) = frame.pe_sign ? 1 : -1; + // Sign of phase-encoding direction needs to reflect fact that DICOM is in LPS but NIfTI/MRtrix are RAS + int pe_sign = frame.pe_sign; + if (frame.pe_axis == 0 || frame.pe_axis == 1) + pe_sign = -pe_sign; + pe_scheme (n, frame.pe_axis) = pe_sign; if (std::isfinite (frame.bandwidth_per_pixel_phase_encode)) { const default_type effective_echo_spacing = 1.0 / (frame.bandwidth_per_pixel_phase_encode * frame.dim[frame.pe_axis]); pe_scheme(n, 3) = effective_echo_spacing * (frame.dim[frame.pe_axis] - 1); From a40ae2c31227cdbc392c15303dd4812d2f7d5a0f Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 12 Aug 2016 17:58:30 +1000 Subject: [PATCH 065/723] Scripts: Restore old -verbose behaviour Calling any Python script with the -verbose option will now show MRtrix-command progress bars in the running terminal. In addition, the runCommand() function may now provide text output from stdout and stderr from the executed command(s) to the script writer. --- scripts/lib/runCommand.py | 70 ++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/scripts/lib/runCommand.py b/scripts/lib/runCommand.py index ef0e9c2679..952e98b329 100644 --- a/scripts/lib/runCommand.py +++ b/scripts/lib/runCommand.py @@ -96,40 +96,48 @@ def runCommand(cmd, exitOnError=True): sys.stdout.write(lib.app.colourConsole + 'Command:' + lib.app.colourClear + ' ' + cmd + '\n') sys.stdout.flush() + # Execute all processes + processes = [ ] + for index, command in enumerate(cmdstack): + if index > 0: + proc_in = processes[index-1].stdout + else: + proc_in = None + process = subprocess.Popen (command, stdin=proc_in, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + processes.append(process) + + return_stdout = '' + return_stderr = '' error = False error_text = '' - # TODO If script is running in verbose mode, ideally want to duplicate stderr output in the terminal - if len(cmdstack) == 1: - process = subprocess.Popen(cmdstack[0], stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (stdoutdata, stderrdata) = process.communicate() + + # Wait for all commands to complete + for process in processes: + + # Switch how we monitor running processes / wait for them to complete + # depending on whether or not the user has specified -verbose option + if lib.app.verbosity > 1: + stderrdata = '' + while True: + # Have to read one character at a time: Waiting for a newline character using e.g. readline() will prevent MRtrix progressbars from appearing + line = process.stderr.read(1).decode('utf-8') + sys.stderr.write(line) + stderrdata += line + if not line and process.poll() != None: + break + (stdoutdata, nullstderrdata) = process.communicate() + stdoutdata = stdoutdata.decode('utf-8') + else: + process.wait() + (stdoutdata, stderrdata) = process.communicate() + stdoutdata = stdoutdata.decode('utf-8') + stderrdata = stderrdata.decode('utf-8') + + return_stdout += stdoutdata + '\n' + return_stderr += stderrdata + '\n' if process.returncode: error = True - error_text = stdoutdata.decode('utf-8') + stderrdata.decode('utf-8') - else: - processes = [ ] - for index, command in enumerate(cmdstack): - if index > 0: - proc_in = processes[index-1].stdout - else: - proc_in = None - process = subprocess.Popen (command, stdin=proc_in, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - processes.append(process) - - # Wait for all commands to complete - for index, process in enumerate(processes): - if index < len(cmdstack)-1: - # Only capture the output if the command failed; otherwise, let it pipe to the next command - process.wait() - if process.returncode: - error = True - (stdoutdata, stderrdata) = process.communicate() - error_text = error_text + stdoutdata.decode('utf-8') + stderrdata.decode('utf-8') - else: - (stdoutdata, stderrdata) = process.communicate() - if process.returncode: - error = True - error_text = error_text + stdoutdata.decode('utf-8') + stderrdata.decode('utf-8') - + error_text += stdoutdata + stderrdata if (error): if exitOnError: @@ -151,3 +159,5 @@ def runCommand(cmd, exitOnError=True): if lib.app.tempDir: with open(os.path.join(lib.app.tempDir, 'log.txt'), 'a') as outfile: outfile.write(cmd + '\n') + + return (return_stdout, return_stderr) From eda9be942f503cc30e86cbfe3c783de2fb5d69cc Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 12 Aug 2016 18:26:12 +1000 Subject: [PATCH 066/723] mrinfo: Add PE table export options --- cmd/mrinfo.cpp | 13 ++++++++++--- docs/reference/commands/mrinfo.rst | 7 +++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/cmd/mrinfo.cpp b/cmd/mrinfo.cpp index e54b3e0e27..ef44f08b32 100644 --- a/cmd/mrinfo.cpp +++ b/cmd/mrinfo.cpp @@ -20,6 +20,7 @@ #include "command.h" #include "header.h" +#include "phase_encoding.h" #include "file/json.h" #include "dwi/gradient.h" @@ -91,7 +92,9 @@ void usage () + Option ("dwgrad", "the diffusion-weighting gradient table, as stored in the header " "(i.e. without any interpretation, scaling of b-values, or normalisation of gradient vectors)") + Option ("shells", "list the average b-value of each shell") - + Option ("shellcounts", "list the number of volumes in each shell"); + + Option ("shellcounts", "list the number of volumes in each shell") + + + PhaseEncoding::ExportOptions; } @@ -165,10 +168,13 @@ void run () { auto check_option_group = [](const App::OptionGroup& g) { for (auto o: g) if (get_options (o.id).size()) return true; return false; }; - bool export_grad = check_option_group (GradExportOptions); + 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"); + if (export_pe && argument.size() > 1) + throw Exception ("can only export phase encoding table to file if a single input image is provided"); std::unique_ptr json (get_options ("json_export").size() ? new nlohmann::json : nullptr); @@ -191,7 +197,7 @@ void run () const bool raw_dwgrad = get_options("raw_dwgrad") .size(); const bool print_full_header = !(format || ndim || size || vox || datatype || stride || - offset || multiplier || properties.size() || transform || dwgrad || export_grad || shells || shellcounts); + offset || multiplier || properties.size() || transform || dwgrad || export_grad || shells || shellcounts || export_pe); for (size_t i = 0; i < argument.size(); ++i) { @@ -229,6 +235,7 @@ void run () print_properties (header, properties[n][0]); DWI::export_grad_commandline (header); + PhaseEncoding::export_commandline (header); if (json) { for (const auto& kv : header.keyval()) { diff --git a/docs/reference/commands/mrinfo.rst b/docs/reference/commands/mrinfo.rst index acb2132a45..bb4e767947 100644 --- a/docs/reference/commands/mrinfo.rst +++ b/docs/reference/commands/mrinfo.rst @@ -74,6 +74,13 @@ DW gradient table export options - **-shellcounts** list the number of volumes in each shell +Options for exporting phase-encode tables +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-export_pe_table file** export phase-encoding table to file + +- **-export_pe_eddy config indices** export phase-encoding information to an EDDY-style config / index file pair + Standard options ^^^^^^^^^^^^^^^^ From 90d1e39d5714a64b2baab0e83801112db0d96840 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 12 Aug 2016 23:44:42 +1000 Subject: [PATCH 067/723] New config options: NIfTI.AutoLoadJSON and NIfTI.AutoSaveJSON These config file entries willl instruct any NIfTI image read/write operations to additionally load/save a corresponding JSON file (same file basename, but .json extension) automatically. Also moved the core JSON file read/write operations to library header files. --- cmd/mrconvert.cpp | 29 ++--------- docs/reference/config_file_options.rst | 10 ++++ lib/file/json_utils.cpp | 67 ++++++++++++++++++++++++++ lib/file/json_utils.h | 38 +++++++++++++++ lib/file/nifti1_utils.cpp | 37 ++++++++++++++ lib/file/nifti2_utils.cpp | 26 ++++++++++ 6 files changed, 183 insertions(+), 24 deletions(-) create mode 100644 lib/file/json_utils.cpp create mode 100644 lib/file/json_utils.h diff --git a/cmd/mrconvert.cpp b/cmd/mrconvert.cpp index 78ae4e0689..4ed989551f 100644 --- a/cmd/mrconvert.cpp +++ b/cmd/mrconvert.cpp @@ -22,7 +22,7 @@ #include "algo/threaded_copy.h" #include "adapter/extract.h" #include "adapter/permute_axes.h" -#include "file/json.h" +#include "file/json_utils.h" #include "file/ofstream.h" #include "dwi/gradient.h" @@ -262,22 +262,8 @@ void run () PhaseEncoding::set_scheme (header_out, PhaseEncoding::get_scheme (header_in)); auto opt = get_options ("json_import"); - if (opt.size()) { - std::ifstream in (opt[0][0]); - if (!in) - throw Exception ("Error opening JSON file \"" + std::string(opt[0][0]) + "\""); - nlohmann::json json; - try { - in >> json; - } catch (...) { - throw Exception ("Error parsing JSON file \"" + std::string(opt[0][0]) + "\""); - } - for (auto i = json.cbegin(); i != json.cend(); ++i) { - // Only load simple parameters at the first level - if (i->is_primitive()) - header_out.keyval().insert (std::make_pair (i.key(), str(i.value()))); - } - } + if (opt.size()) + File::JSON::load (header_out, opt[0][0]); { opt = get_options ("header_cat"); @@ -367,13 +353,8 @@ void run () opt = get_options ("json_export"); - if (opt.size()) { - nlohmann::json json; - for (const auto& kv : header_out.keyval()) - json[kv.first] = kv.second; - File::OFStream out (opt[0][0]); - out << json.dump(4); - } + if (opt.size()) + File::JSON::save (header_out, opt[0][0]); if (header_out.intensity_offset() == 0.0 && header_out.intensity_scale() == 1.0 && !header_out.datatype().is_floating_point()) { diff --git a/docs/reference/config_file_options.rst b/docs/reference/config_file_options.rst index bb5a953b1c..f36248f8ae 100644 --- a/docs/reference/config_file_options.rst +++ b/docs/reference/config_file_options.rst @@ -289,6 +289,16 @@ List of MRtrix3 configuration file options A boolean value to indicate whether NIfTI images should always be written in the new NIfTI-2 format. If false, images will be written in the older NIfTI-1 format by default, with the exception being files where the number of voxels along any axis exceeds the maximum permissible in that format (32767), in which case the output file will automatically switch to the NIfTI-2 format. +* **NIfTI.AutoLoadJSON** + *default: 0 (false)* + + A boolean value to indicate whether, when opening NIfTI images, any corresponding JSON file should be automatically loaded + +* **NIfTI.AutoSaveJSON** + *default: 0 (false)* + + A boolean value to indicate whether, when writing NIfTI images, a corresponding JSON file should be automatically created in order to save any header entries that cannot be stored in the NIfTI header + * **NeedOpenGLCoreProfile** *default: 1 (true)* diff --git a/lib/file/json_utils.cpp b/lib/file/json_utils.cpp new file mode 100644 index 0000000000..62ad885036 --- /dev/null +++ b/lib/file/json_utils.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include + +#include "file/json_utils.h" + +#include "exception.h" +#include "header.h" +#include "file/ofstream.h" + +namespace MR +{ + namespace File + { + namespace JSON + { + + + + void load (Header& H, const std::string& path) + { + std::ifstream in (path); + if (!in) + throw Exception ("Error opening JSON file \"" + path + "\""); + nlohmann::json json; + try { + in >> json; + } catch (...) { + throw Exception ("Error parsing JSON file \"" + path + "\""); + } + for (auto i = json.cbegin(); i != json.cend(); ++i) { + // Only load simple parameters at the first level + if (i->is_primitive()) + H.keyval().insert (std::make_pair (i.key(), str(i.value()))); + } + } + + + + void save (const Header& H, const std::string& path) + { + nlohmann::json json; + for (const auto& kv : H.keyval()) + json[kv.first] = kv.second; + File::OFStream out (path); + out << json.dump(4); + } + + + + } + } +} + diff --git a/lib/file/json_utils.h b/lib/file/json_utils.h new file mode 100644 index 0000000000..cb5d6a7fcb --- /dev/null +++ b/lib/file/json_utils.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __file_json_utils_h__ +#define __file_json_utils_h__ + +#include "file/json.h" + +namespace MR +{ + class Header; + + namespace File + { + namespace JSON + { + + void load (Header&, const std::string&); + void save (const Header&, const std::string&); + + } + } +} + +#endif + diff --git a/lib/file/nifti1_utils.cpp b/lib/file/nifti1_utils.cpp index 66aabfc2e6..4fd66aae04 100644 --- a/lib/file/nifti1_utils.cpp +++ b/lib/file/nifti1_utils.cpp @@ -16,6 +16,7 @@ #include "header.h" #include "raw.h" #include "file/config.h" +#include "file/json_utils.h" #include "file/nifti_utils.h" #include "file/nifti1_utils.h" @@ -214,6 +215,24 @@ namespace MR if (qfac < 0.0) H.transform().matrix().col(2) *= qfac; } + + //CONF option: NIfTI.AutoLoadJSON + //CONF default: 0 (false) + //CONF A boolean value to indicate whether, when opening NIfTI images, + //CONF any corresponding JSON file should be automatically loaded + if (File::Config::get_bool ("NIfTI.AutoLoadJSON", false)) { + std::string json_path = H.name(); + if (Path::has_suffix (json_path, ".nii.gz")) + json_path = json_path.substr (0, json_path.size()-7); + else if (Path::has_suffix (json_path, ".nii")) + json_path = json_path.substr (0, json_path.size()-4); + else + assert (0); + json_path += ".json"; + if (Path::exists (json_path)) + File::JSON::load (H, json_path); + } + } else { H.transform()(0,0) = std::numeric_limits::quiet_NaN(); @@ -398,6 +417,24 @@ namespace MR Raw::store (M (2,3), &NH.srow_z[3], is_BE); strncpy ( (char*) &NH.magic, single_file ? "n+1\0" : "ni1\0", 4); + + //CONF option: NIfTI.AutoSaveJSON + //CONF default: 0 (false) + //CONF A boolean value to indicate whether, when writing NIfTI images, + //CONF a corresponding JSON file should be automatically created in order + //CONF to save any header entries that cannot be stored in the NIfTI + //CONF header + if (single_file && File::Config::get_bool ("NIfTI.AutoSaveJSON", false)) { + std::string json_path = H.name(); + if (Path::has_suffix (json_path, ".nii.gz")) + json_path = json_path.substr (0, json_path.size()-7); + else if (Path::has_suffix (json_path, ".nii")) + json_path = json_path.substr (0, json_path.size()-4); + else + assert (0); + json_path += ".json"; + File::JSON::save (H, json_path); + } } diff --git a/lib/file/nifti2_utils.cpp b/lib/file/nifti2_utils.cpp index f5ec2256e3..836a84a162 100644 --- a/lib/file/nifti2_utils.cpp +++ b/lib/file/nifti2_utils.cpp @@ -16,6 +16,7 @@ #include "header.h" #include "raw.h" #include "file/config.h" +#include "file/json_utils.h" #include "file/nifti1.h" #include "file/nifti_utils.h" #include "file/nifti2_utils.h" @@ -208,6 +209,19 @@ namespace MR H.transform().matrix().col(2) *= qfac; } + if (File::Config::get_bool ("NIfTI.AutoLoadJSON", false)) { + std::string json_path = H.name(); + if (Path::has_suffix (json_path, ".nii.gz")) + json_path = json_path.substr (0, json_path.size()-7); + else if (Path::has_suffix (json_path, ".nii")) + json_path = json_path.substr (0, json_path.size()-4); + else + assert (0); + json_path += ".json"; + if (Path::exists (json_path)) + File::JSON::load (H, json_path); + } + return data_offset; } @@ -368,6 +382,18 @@ namespace MR const char xyzt_units[4] { NIFTI_UNITS_MM, NIFTI_UNITS_MM, NIFTI_UNITS_MM, NIFTI_UNITS_SEC }; const int32_t* const xyzt_units_as_int_ptr = reinterpret_cast(xyzt_units); Raw::store (*xyzt_units_as_int_ptr, &NH.xyzt_units, is_BE); + + if (File::Config::get_bool ("NIfTI.AutoSaveJSON", false)) { + std::string json_path = H.name(); + if (Path::has_suffix (json_path, ".nii.gz")) + json_path = json_path.substr (0, json_path.size()-7); + else if (Path::has_suffix (json_path, ".nii")) + json_path = json_path.substr (0, json_path.size()-4); + else + assert (0); + json_path += ".json"; + File::JSON::save (H, json_path); + } } From c80420a651e413ce35a8450abd71f22881b4da8d Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Mon, 15 Aug 2016 11:49:42 +1000 Subject: [PATCH 068/723] FMLS: remove min peak amp threshold --- src/dwi/fmls.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dwi/fmls.cpp b/src/dwi/fmls.cpp index 72e660b1c0..ff0dec2b6d 100644 --- a/src/dwi/fmls.cpp +++ b/src/dwi/fmls.cpp @@ -276,7 +276,7 @@ namespace MR { for (auto i = out.begin(); i != out.end();) { // Empty increment - if (i->is_negative() || i->get_peak_value() < std::max (min_peak_amp, peak_value_threshold) || i->get_integral() < min_integral) { + if (i->is_negative() || i->get_peak_value() < peak_value_threshold || i->get_integral() < min_integral) { i = out.erase (i); } else { const dir_t peak_bin (i->get_peak_dir_bin()); From 5e52fb7b9247fa4310ebf9807a2bc6e310afccba Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Mon, 15 Aug 2016 11:51:02 +1000 Subject: [PATCH 069/723] initial changes to fixelreorient new fixel format. Not yet working --- cmd/fixelcrop.cpp | 2 +- cmd/fixelreorient.cpp | 26 ++++++++++++++++---------- scripts/lib/cmdlineParser.py | 2 +- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/cmd/fixelcrop.cpp b/cmd/fixelcrop.cpp index d1efcc2656..eca017c21a 100644 --- a/cmd/fixelcrop.cpp +++ b/cmd/fixelcrop.cpp @@ -29,7 +29,7 @@ using namespace App; void usage () { - AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; + AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) & Rami Tabarra (rami.tabarra@florey.edu.au)"; DESCRIPTION + "Crop a fixel index image along with corresponding fixel data images"; diff --git a/cmd/fixelreorient.cpp b/cmd/fixelreorient.cpp index 4367f01117..0fb85bb8fd 100644 --- a/cmd/fixelreorient.cpp +++ b/cmd/fixelreorient.cpp @@ -25,38 +25,44 @@ using namespace MR; using namespace App; -using Sparse::FixelMetric; +#include "fixel_format/helpers.h" +#include "fixel_format/keys.h" void usage () { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; DESCRIPTION - + "Reorient fixel directions using the local affine transformation (Jacobian matrix) of an input warp."; + + "Inplace reorientation of fixel directions using the local affine transformation (Jacobian matrix) at each voxel within an input warp."; ARGUMENTS - + Argument ("input", "the input fixel image.").type_image_in () + + Argument ("fixel_in", "the fixel folder").type_text () + 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 ("output", "the output fixel image.").type_image_out (); + "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 ("dir_out", "the output fixel folder. If the the input and output folders are the same the directions file will " + "replaced. If a new folder is supplied then all fixel data will be copied to the new folder.").type_text (); } void run () { - auto input_header = Header::open (argument[0]); - Sparse::Image input_fixel (argument[0]); + const auto fixel_folder = argument[0]; + FixelFormat::check_fixel_folder (fixel_folder); Header warp_header = Header::open(argument[1]); - check_dimensions (input_fixel, warp_header, 0, 3); + const auto out_fixel_folder = argument[2]; + auto index_image = FixelFormat::find_index_header (fixel_folder).get_image (); + + check_dimensions (index_image, warp_header, 0, 3); if (warp_header.ndim() != 4) throw Exception ("The input deformation field image must be a 4D file."); if (warp_header.size(3) != 3) - check_dimensions (warp_header, input_fixel, 0, 3); Adapter::Jacobian > jacobian (warp_header.get_image()); + + Sparse::Image output_fixel (argument[2], input_header); for (auto i = Loop ("reorienting fixel directions", input_fixel) (input_fixel, jacobian, output_fixel); i; ++i) { diff --git a/scripts/lib/cmdlineParser.py b/scripts/lib/cmdlineParser.py index ba45ccf80e..2ec6e0fe46 100644 --- a/scripts/lib/cmdlineParser.py +++ b/scripts/lib/cmdlineParser.py @@ -108,7 +108,7 @@ def underline(text): w = textwrap.TextWrapper(width=80, initial_indent=' ', subsequent_indent=' ') w_arg = textwrap.TextWrapper(width=80, initial_indent='', subsequent_indent=' ') - s = ' ' + bold(self.prog) + ': Script using the MRtrix3 Python libraries\n' + s = ' ' + bold(self.prog) + ': Script using the MRtrix3 Python library\n' s += '\n' s += bold('SYNOPSIS') + '\n' s += '\n' From 62b6c9c68125a81899554a7bf1eeefae5480e407 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Mon, 15 Aug 2016 14:17:10 +1000 Subject: [PATCH 070/723] Added warp helpers file for checking input --- cmd/mrtransform.cpp | 9 +--- cmd/warp2metric.cpp | 6 +-- cmd/warpconvert.cpp | 19 ++------- cmd/warpcorrect.cpp | 7 +--- lib/registration/nonlinear.h | 2 +- lib/registration/warp/compose.h | 2 +- lib/registration/warp/helpers.h | 73 +++++++++++++++++++++++++++++++++ 7 files changed, 85 insertions(+), 33 deletions(-) create mode 100644 lib/registration/warp/helpers.h diff --git a/cmd/mrtransform.cpp b/cmd/mrtransform.cpp index de6c391b6d..93e435bd4d 100644 --- a/cmd/mrtransform.cpp +++ b/cmd/mrtransform.cpp @@ -31,7 +31,7 @@ #include "dwi/directions/predefined.h" #include "dwi/gradient.h" #include "registration/transform/reorient.h" -#include "registration/warp/utils.h" +#include "registration/warp/helpers.h" #include "registration/warp/compose.h" #include "math/average_space.h" @@ -234,12 +234,7 @@ void run () Image warp; if (opt.size()) { warp = Image::open (opt[0][0]).with_direct_io(); - if (warp.ndim() != 5) - throw Exception ("the input -warp_full image must be a 5D file."); - if (warp.size(3) != 3) - throw Exception ("the input -warp_full image must have 3 volumes (x,y,z) in the 4th dimension."); - if (warp.size(4) != 4) - throw Exception ("the input -warp_full image must have 4 volumes in the 5th dimension."); + 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"); diff --git a/cmd/warp2metric.cpp b/cmd/warp2metric.cpp index 968c1b7bcf..9df886c409 100644 --- a/cmd/warp2metric.cpp +++ b/cmd/warp2metric.cpp @@ -21,6 +21,7 @@ #include "sparse/fixel_metric.h" #include "sparse/image.h" #include "adapter/jacobian.h" +#include "registration/warp/helpers.h" using namespace MR; using namespace App; @@ -61,10 +62,7 @@ typedef float value_type; void run () { auto input = Image::open (argument[0]).with_direct_io (3); - if (input.ndim() != 4) - throw Exception ("input deformation field is not a 4D image"); - if (input.size(3) != 3) - throw Exception ("input deformation field should have 3 volumes in the 4th dimension"); + Registration::Warp::check_warp (input); std::unique_ptr > jmatrix_output; std::unique_ptr > jdeterminant_output; diff --git a/cmd/warpconvert.cpp b/cmd/warpconvert.cpp index 163c2189bc..f0e7c13cd1 100644 --- a/cmd/warpconvert.cpp +++ b/cmd/warpconvert.cpp @@ -16,7 +16,7 @@ #include "command.h" #include "image.h" -#include "registration/warp/utils.h" +#include "registration/warp/helpers.h" #include "registration/warp/compose.h" #include "registration/warp/convert.h" #include "adapter/extract.h" @@ -92,10 +92,7 @@ void run () WARN ("-from option ignored with deformation2displacement conversion type"); auto deformation = Image::open (argument[0]).with_direct_io (3); - if (deformation.ndim() != 4) - throw Exception ("invalid input image. The input deformation field image must be a 4D file."); - if (deformation.size(3) != 3) - throw Exception ("invalid input image. The input deformation field image must have 3 volumes (x,y,z) in the 4th dimension."); + Registration::Warp::check_warp (deformation); Header header (deformation); header.datatype() = DataType::from_command_line (DataType::Float32); @@ -105,10 +102,7 @@ void run () // displacement2deformation } else if (registration_type == 1) { auto displacement = Image::open (argument[0]).with_direct_io (3); - if (displacement.ndim() != 4) - throw Exception ("invalid input image. The input displacement field image must be a 4D file."); - if (displacement.size(3) != 3) - throw Exception ("invalid input image. The input displacement field image must have 3 volumes (x,y,z) in the 4th dimension."); + Registration::Warp::check_warp (displacement); if (midway_space) WARN ("-midway_space option ignored with displacement2deformation conversion type"); @@ -126,12 +120,7 @@ void run () } else if (registration_type == 2 || registration_type == 3) { auto warp = Image::open (argument[0]).with_direct_io (3); - if (warp.ndim() != 5) - throw Exception ("invalid input image. The input warpfull field image must be a 5D file."); - if (warp.size(3) != 3) - throw Exception ("invalid input image. The input warpfull field image must have 3 volumes (x,y,z) in the 4th dimension."); - if (warp.size(4) != 4) - throw Exception ("invalid input image. The input warpfull field image must have 4 volumes in the 5th dimension."); + Registration::Warp::check_warp_full (warp); Image warp_output; if (midway_space) { diff --git a/cmd/warpcorrect.cpp b/cmd/warpcorrect.cpp index 75179af591..54cc3342b6 100644 --- a/cmd/warpcorrect.cpp +++ b/cmd/warpcorrect.cpp @@ -17,6 +17,7 @@ #include "command.h" #include "image.h" #include "algo/threaded_loop.h" +#include "registration/warp/helpers.h" using namespace MR; @@ -44,11 +45,7 @@ typedef float value_type; void run () { auto input = Image::open (argument[0]).with_direct_io (3); - if (input.ndim() != 4) - throw Exception ("input warp is not a 4D image"); - - if (input.size(3) != 3) - throw Exception ("input warp should have 3 volumes in the 4th dimension"); + Registration::Warp::check_warp (input); auto output = Image::create (argument[1], input); diff --git a/lib/registration/nonlinear.h b/lib/registration/nonlinear.h index 1d82122606..bc43f38364 100644 --- a/lib/registration/nonlinear.h +++ b/lib/registration/nonlinear.h @@ -25,7 +25,7 @@ #include "registration/transform/affine.h" #include "registration/warp/compose.h" #include "registration/warp/convert.h" -#include "registration/warp/utils.h" +#include "registration/warp/helpers.h" #include "registration/warp/invert.h" #include "registration/metric/demons.h" #include "registration/metric/demons4D.h" diff --git a/lib/registration/warp/compose.h b/lib/registration/warp/compose.h index 078d68bc67..bcf5b6add2 100644 --- a/lib/registration/warp/compose.h +++ b/lib/registration/warp/compose.h @@ -21,7 +21,7 @@ #include "transform.h" #include "interp/linear.h" #include "adapter/jacobian.h" //TODO remove after debug -#include "registration/warp/utils.h" +#include "registration/warp/helpers.h" #include "adapter/extract.h" namespace MR diff --git a/lib/registration/warp/helpers.h b/lib/registration/warp/helpers.h new file mode 100644 index 0000000000..d8aeaa8e36 --- /dev/null +++ b/lib/registration/warp/helpers.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __registration_warp_utils_h__ +#define __registration_warp_utils_h__ + +namespace MR +{ + namespace Registration + { + + namespace Warp + { + + template + inline void check_warp (const HeaderType& warp_header) + { + if (warp_header.ndim() != 4) + throw Exception ("input warp is not a 4D image"); + if (warp_header.size(3) != 3) + throw Exception ("input warp should have 3 volumes in the 4th dimension"); + } + + template + inline void check_warp_full (const HeaderType& warp_header) + { + if (warp_header.ndim() != 5) + throw Exception ("the input warp image must be a 5D file."); + if (warp_header.size(3) != 3) + throw Exception ("the input warp image must have 3 volumes (x,y,z) in the 4th dimension."); + if (warp_header.size(4) != 4) + throw Exception ("the input warp image must have 4 volumes in the 5th dimension."); + } + + template + transform_type parse_linear_transform (InputWarpType& input_warps, std::string name) { + transform_type linear; + const auto it = input_warps.keyval().find (name); + if (it != input_warps.keyval().end()) { + const auto lines = split_lines (it->second); + if (lines.size() != 3) + throw Exception ("linear transform in initialisation syn warps image header does not contain 3 rows"); + for (size_t row = 0; row < 3; ++row) { + const auto values = split (lines[row], " ", true); + if (values.size() != 4) + throw Exception ("linear transform in initialisation syn warps image header does not contain 4 columns"); + for (size_t col = 0; col < 4; ++col) + linear (row, col) = std::stod (values[col]); + } + } else { + throw Exception ("no linear transform found in initialisation syn warps image header"); + } + + return linear; + } + + } + } +} + +#endif From e8cec655dfed9db88b67ac60a1d178b9a7c663ea Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 16 Aug 2016 04:51:31 +1000 Subject: [PATCH 071/723] Scripts: Add -debug option This option adds additional debugging information to the terminal over and above the output presented when using the -verbose option; it also passes the -info flag to any MRtrix commands. Other changes: - Changed the colour used by runCommand() to cyan (to not conflict with the blue used for debug, which is consistent with MRtrix binaries). Fixed a race condition where sometimes stdout data would be pulled from a process before the trailing process was able to read it via stdin. - Changed a few terminal outputs to use stderr rather than stdout. --- docs/reference/scripts/5ttgen.rst | 12 +++-- docs/reference/scripts/dwi2response.rst | 24 ++++++--- docs/reference/scripts/dwibiascorrect.rst | 4 +- docs/reference/scripts/dwiintensitynorm.rst | 4 +- docs/reference/scripts/dwipreproc.rst | 4 +- docs/reference/scripts/labelsgmfix.rst | 4 +- .../reference/scripts/population_template.rst | 4 +- scripts/lib/app.py | 51 +++++++++++-------- scripts/lib/binaryInPath.py | 2 + scripts/lib/cmdlineParser.py | 5 +- scripts/lib/debugMessage.py | 10 ++++ scripts/lib/delFile.py | 3 +- scripts/lib/getAlgorithmList.py | 6 ++- scripts/lib/getFSLEddyPath.py | 2 + scripts/lib/getFSLSuffix.py | 3 ++ scripts/lib/getHeaderInfo.py | 7 ++- scripts/lib/getHeaderProperty.py | 27 +++++----- scripts/lib/getImageStat.py | 1 + scripts/lib/getPEDir.py | 31 ++++++----- scripts/lib/getTempImagePath.py | 4 +- scripts/lib/getUserPath.py | 6 ++- scripts/lib/imagesMatch.py | 6 +++ scripts/lib/isWindows.py | 15 ++++-- scripts/lib/printMessage.py | 3 +- scripts/lib/readMRtrixConfSetting.py | 12 ++++- scripts/lib/runCommand.py | 30 ++++++----- scripts/lib/warnMessage.py | 3 +- 27 files changed, 190 insertions(+), 93 deletions(-) create mode 100644 scripts/lib/debugMessage.py diff --git a/docs/reference/scripts/5ttgen.rst b/docs/reference/scripts/5ttgen.rst index 74e1f72f4f..662d5ed40c 100644 --- a/docs/reference/scripts/5ttgen.rst +++ b/docs/reference/scripts/5ttgen.rst @@ -50,7 +50,9 @@ Standard options - **-quiet** Suppress all console output during script execution -- **-verbose** Display additional information for every command invoked +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output References ^^^^^^^^^^ @@ -139,7 +141,9 @@ Standard options - **-quiet** Suppress all console output during script execution -- **-verbose** Display additional information for every command invoked +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output References ^^^^^^^^^^ @@ -211,7 +215,9 @@ Standard options - **-quiet** Suppress all console output during script execution -- **-verbose** Display additional information for every command invoked +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output References ^^^^^^^^^^ diff --git a/docs/reference/scripts/dwi2response.rst b/docs/reference/scripts/dwi2response.rst index 4dcf6d9f67..aa948b6ec9 100644 --- a/docs/reference/scripts/dwi2response.rst +++ b/docs/reference/scripts/dwi2response.rst @@ -62,7 +62,9 @@ Standard options - **-quiet** Suppress all console output during script execution -- **-verbose** Display additional information for every command invoked +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output References ^^^^^^^^^^ @@ -154,7 +156,9 @@ Standard options - **-quiet** Suppress all console output during script execution -- **-verbose** Display additional information for every command invoked +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output References ^^^^^^^^^^ @@ -254,7 +258,9 @@ Standard options - **-quiet** Suppress all console output during script execution -- **-verbose** Display additional information for every command invoked +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output References ^^^^^^^^^^ @@ -349,7 +355,9 @@ Standard options - **-quiet** Suppress all console output during script execution -- **-verbose** Display additional information for every command invoked +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output References ^^^^^^^^^^ @@ -446,7 +454,9 @@ Standard options - **-quiet** Suppress all console output during script execution -- **-verbose** Display additional information for every command invoked +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output References ^^^^^^^^^^ @@ -516,7 +526,9 @@ Standard options - **-quiet** Suppress all console output during script execution -- **-verbose** Display additional information for every command invoked +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output References ^^^^^^^^^^ diff --git a/docs/reference/scripts/dwibiascorrect.rst b/docs/reference/scripts/dwibiascorrect.rst index 87e6ffefdd..1fcea73841 100644 --- a/docs/reference/scripts/dwibiascorrect.rst +++ b/docs/reference/scripts/dwibiascorrect.rst @@ -53,7 +53,9 @@ Standard options - **-quiet** Suppress all console output during script execution -- **-verbose** Display additional information for every command invoked +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output References ^^^^^^^^^^ diff --git a/docs/reference/scripts/dwiintensitynorm.rst b/docs/reference/scripts/dwiintensitynorm.rst index 867ed69d33..a64c13472d 100644 --- a/docs/reference/scripts/dwiintensitynorm.rst +++ b/docs/reference/scripts/dwiintensitynorm.rst @@ -46,7 +46,9 @@ Standard options - **-quiet** Suppress all console output during script execution -- **-verbose** Display additional information for every command invoked +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output -------------- diff --git a/docs/reference/scripts/dwipreproc.rst b/docs/reference/scripts/dwipreproc.rst index 27c393b721..19c01238d7 100644 --- a/docs/reference/scripts/dwipreproc.rst +++ b/docs/reference/scripts/dwipreproc.rst @@ -61,7 +61,9 @@ Standard options - **-quiet** Suppress all console output during script execution -- **-verbose** Display additional information for every command invoked +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output References ^^^^^^^^^^ diff --git a/docs/reference/scripts/labelsgmfix.rst b/docs/reference/scripts/labelsgmfix.rst index 67cd302e83..d5bc7bf9ec 100644 --- a/docs/reference/scripts/labelsgmfix.rst +++ b/docs/reference/scripts/labelsgmfix.rst @@ -47,7 +47,9 @@ Standard options - **-quiet** Suppress all console output during script execution -- **-verbose** Display additional information for every command invoked +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output References ^^^^^^^^^^ diff --git a/docs/reference/scripts/population_template.rst b/docs/reference/scripts/population_template.rst index 20d2c7535a..b7e5e538be 100644 --- a/docs/reference/scripts/population_template.rst +++ b/docs/reference/scripts/population_template.rst @@ -79,7 +79,9 @@ Standard options - **-quiet** Suppress all console output during script execution -- **-verbose** Display additional information for every command invoked +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output -------------- diff --git a/scripts/lib/app.py b/scripts/lib/app.py index d9c9cb3487..bdcd2ed8e5 100644 --- a/scripts/lib/app.py +++ b/scripts/lib/app.py @@ -17,7 +17,7 @@ externalCitations = False lastFile = '' mrtrixForce = '' -mrtrixQuiet = ' -quiet' +mrtrixVerbosity = ' -quiet' mrtrixNThreads = '' parser = '' tempDir = '' @@ -26,6 +26,7 @@ colourClear = '' colourConsole = '' +colourDebug = '' colourError = '' colourPrint = '' colourWarn = '' @@ -45,8 +46,8 @@ def initialise(): from lib.errorMessage import errorMessage from lib.printMessage import printMessage from lib.readMRtrixConfSetting import readMRtrixConfSetting - global args, citationList, cleanup, externalCitations, lastFile, mrtrixNThreads, mrtrixQuiet, parser, tempDir, verbosity, workingDir - global colourClear, colourConsole, colourError, colourPrint, colourWarn + global args, citationList, cleanup, externalCitations, lastFile, mrtrixNThreads, mrtrixVerbosity, parser, tempDir, verbosity, workingDir + global colourClear, colourConsole, colourDebug, colourError, colourPrint, colourWarn if not parser: errorMessage('Script error: Command-line parser must be initialised before app') @@ -78,7 +79,8 @@ def initialise(): use_colour = True if use_colour: colourClear = '\033[0m' - colourConsole = '\033[03;34m' + colourConsole = '\033[03;36m' + colourDebug = '\033[03;34m' colourError = '\033[01;31m' colourPrint = '\033[03;32m' colourWarn = '\033[00;31m' @@ -89,10 +91,13 @@ def initialise(): mrtrixNThreads = ' -nthreads ' + args.nthreads if args.quiet: verbosity = 0 - mrtrixQuiet = ' -quiet' + mrtrixVerbosity = ' -quiet' elif args.verbose: verbosity = 2 - mrtrixQuiet = '' + mrtrixVerbosity = '' + elif args.debug: + verbosity = 3 + mrtrixVerbosity = ' -info' if citationList: printMessage('') @@ -198,25 +203,31 @@ def complete(): def make_dir(dir): import os + from lib.debugMessage import debugMessage if not os.path.exists(dir): os.makedirs(dir) + debugMessage('Created directory ' + dir) + else: + debugMessage('Directory ' + dir + ' already exists') # determines the common postfix for a list of filenames (including the file extension) def getCommonPostfix(inputFiles): - first = inputFiles[0]; - cursor = 0 - found = False; - common = '' - for i in reversed(first): - if found == False: - for j in inputFiles: - if j[len(j)-cursor-1] != first[len(first)-cursor-1]: - found = True - break - if found == False: - common = first[len(first)-cursor-1] + common - cursor += 1 - return common + from lib.debugMessage import debugMessage + first = inputFiles[0]; + cursor = 0 + found = False; + common = '' + for i in reversed(first): + if found == False: + for j in inputFiles: + if j[len(j)-cursor-1] != first[len(first)-cursor-1]: + found = True + break + if found == False: + common = first[len(first)-cursor-1] + common + cursor += 1 + debugMessage('Common postfix of ' + str(len(inputFiles)) + ' is \'' + common + '\'') + return common diff --git a/scripts/lib/binaryInPath.py b/scripts/lib/binaryInPath.py index 6e00a3e6b5..67fb12b972 100644 --- a/scripts/lib/binaryInPath.py +++ b/scripts/lib/binaryInPath.py @@ -4,6 +4,8 @@ def binaryInPath(cmdname): path_dir = path_dir.strip('"') full_path = os.path.join(path_dir, cmdname) if os.path.isfile(full_path) and os.access(full_path, os.X_OK): + debugMessage('Command \'' + cmdname + '\' found in PATH') return True + debugMessage('Command \'' + cmdname + '\' NOT found in PATH') return False diff --git a/scripts/lib/cmdlineParser.py b/scripts/lib/cmdlineParser.py index cb006678d4..1569c7a340 100644 --- a/scripts/lib/cmdlineParser.py +++ b/scripts/lib/cmdlineParser.py @@ -21,8 +21,9 @@ def initialise(desc): standard_options.add_argument('-nthreads', metavar='number', help='Use this number of threads in MRtrix multi-threaded applications (0 disables multi-threading)') standard_options.add_argument('-tempdir', metavar='/path/to/tmp/', help='Manually specify the path in which to generate the temporary directory') standard_options.add_argument('-quiet', action='store_true', help='Suppress all console output during script execution') - standard_options.add_argument('-verbose', action='store_true', help='Display additional information for every command invoked') - flagMutuallyExclusiveOptions( [ 'quiet', 'verbose' ] ) + standard_options.add_argument('-verbose', action='store_true', help='Display additional information and progress for every command invoked') + standard_options.add_argument('-debug', action='store_true', help='Display additional debugging information over and above the verbose output') + flagMutuallyExclusiveOptions( [ 'quiet', 'verbose', 'debug' ] ) diff --git a/scripts/lib/debugMessage.py b/scripts/lib/debugMessage.py new file mode 100644 index 0000000000..3757359f65 --- /dev/null +++ b/scripts/lib/debugMessage.py @@ -0,0 +1,10 @@ +def debugMessage(message): + import lib.app, inspect, os, sys, types + if lib.app.verbosity <= 2: return + stack = inspect.stack()[1] + try: + fname = stack.function + except: # Prior to Version 3.5 + fname = stack[3] + sys.stderr.write(os.path.basename(sys.argv[0]) + ': ' + lib.app.colourDebug + '[DEBUG] ' + fname + '(): ' + message + lib.app.colourClear + '\n') + diff --git a/scripts/lib/delFile.py b/scripts/lib/delFile.py index 028484cf1a..e3ba935a5e 100644 --- a/scripts/lib/delFile.py +++ b/scripts/lib/delFile.py @@ -3,6 +3,7 @@ def delFile(path): from lib.printMessage import printMessage if not lib.app.cleanup: return - printMessage('Deleting file: ' + path) + if lib.app.verbosity > 1: + printMessage('Deleting file: ' + path) os.remove(path) diff --git a/scripts/lib/getAlgorithmList.py b/scripts/lib/getAlgorithmList.py index bcc3b37d86..ecac0916da 100644 --- a/scripts/lib/getAlgorithmList.py +++ b/scripts/lib/getAlgorithmList.py @@ -1,5 +1,6 @@ def getAlgorithmList(): import os, sys + from lib.debugMessage import debugMessage # Build an initial list of possible algorithms, found in the relevant scripts/src/ directory algorithm_list = [] path = os.path.basename(sys.argv[0]) @@ -10,5 +11,6 @@ def getAlgorithmList(): filename = filename.split('.') if len(filename) == 2 and filename[1] == 'py' and not filename[0] == '__init__': algorithm_list.append(filename[0]) - return sorted(algorithm_list) - + algorithm_list = sorted(algorithm_list) + debugMessage('Found algorithms: ' + str(algorithm_list)) + return algorithm_list diff --git a/scripts/lib/getFSLEddyPath.py b/scripts/lib/getFSLEddyPath.py index 1c0015e9d9..732ee1fb51 100644 --- a/scripts/lib/getFSLEddyPath.py +++ b/scripts/lib/getFSLEddyPath.py @@ -5,9 +5,11 @@ def getFSLEddyPath(cuda): from lib.warnMessage import warnMessage if cuda: if binaryInPath('eddy_cuda'): + debugMessage('Selecting CUDA version of eddy') return 'eddy_cuda' else: warnMessage('CUDA version of eddy not found; running standard version') + debugMessage('Selecting non-CUDA version of eddy') if binaryInPath('eddy_openmp'): return 'eddy_openmp' if binaryInPath('eddy'): diff --git a/scripts/lib/getFSLSuffix.py b/scripts/lib/getFSLSuffix.py index a8653f0a18..38c4c8543a 100644 --- a/scripts/lib/getFSLSuffix.py +++ b/scripts/lib/getFSLSuffix.py @@ -3,10 +3,13 @@ def getFSLSuffix(): from lib.warnMessage import warnMessage fsl_output_type = os.environ.get('FSLOUTPUTTYPE', '') if fsl_output_type == 'NIFTI': + debugMessage('NIFTI -> .nii') return '.nii' if fsl_output_type == 'NIFTI_GZ': + debugMessage('NIFTI_GZ -> .nii.gz') return '.nii.gz' if fsl_output_type == 'NIFTI_PAIR': + debugMessage('NIFTI_PAIR -> .img') return '.img' if fsl_output_type == 'NIFTI_PAIR_GZ': sys.stderr.write('MRtrix does not support compressed NIFTI pairs; please set FSLOUTPUTTYPE to something else\n') diff --git a/scripts/lib/getHeaderInfo.py b/scripts/lib/getHeaderInfo.py index 31ac86e959..b2e0922f3b 100644 --- a/scripts/lib/getHeaderInfo.py +++ b/scripts/lib/getHeaderInfo.py @@ -1,5 +1,6 @@ def getHeaderInfo(image_path, header_item): import lib.app, subprocess + from lib.debugMessage import debugMessage from lib.printMessage import printMessage command = [ 'mrinfo', image_path, '-' + header_item ] if lib.app.verbosity > 1: @@ -8,6 +9,10 @@ def getHeaderInfo(image_path, header_item): result, err = proc.communicate() result = result.rstrip().decode('utf-8') if lib.app.verbosity > 1: - printMessage('Result: ' + result) + if '\n' in result: + printMessage('Result: (' + str(result.count('\n')+1) + ' lines)') + debugMessage(result) + else: + printMessage('Result: ' + result) return result diff --git a/scripts/lib/getHeaderProperty.py b/scripts/lib/getHeaderProperty.py index 52f50f60e3..5e318aeb2f 100644 --- a/scripts/lib/getHeaderProperty.py +++ b/scripts/lib/getHeaderProperty.py @@ -1,13 +1,14 @@ -def getHeaderProperty(image_path, key): - import lib.app, subprocess - from lib.printMessage import printMessage - command = [ 'mrinfo', image_path, '-property', key ] - if lib.app.verbosity > 1: - printMessage('Command: \'' + ' '.join(command) + '\' (piping data to local storage)') - proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=None) - result, err = proc.communicate() - result = result.rstrip().decode('utf-8') - if lib.app.verbosity > 1: - printMessage('Result: ' + result) - return result - +def getHeaderProperty(image_path, key): + import lib.app, subprocess + from lib.debugMessage import debugMessage + from lib.printMessage import printMessage + command = [ 'mrinfo', image_path, '-property', key ] + if lib.app.verbosity > 1: + printMessage('Command: \'' + ' '.join(command) + '\' (piping data to local storage)') + proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=None) + result, err = proc.communicate() + result = result.rstrip().decode('utf-8') + if lib.app.verbosity > 1: + printMessage('Result: ' + result) + return result + diff --git a/scripts/lib/getImageStat.py b/scripts/lib/getImageStat.py index 8666d5c925..7e84822e83 100644 --- a/scripts/lib/getImageStat.py +++ b/scripts/lib/getImageStat.py @@ -1,5 +1,6 @@ def getImageStat(image_path, statistic, mask_path = ''): import lib.app, subprocess + from lib.debugMessage import debugMessage from lib.printMessage import printMessage command = [ 'mrstats', image_path, '-output', statistic ] if mask_path: diff --git a/scripts/lib/getPEDir.py b/scripts/lib/getPEDir.py index 82c2237d34..3736a36a9a 100644 --- a/scripts/lib/getPEDir.py +++ b/scripts/lib/getPEDir.py @@ -1,37 +1,40 @@ def getPEDir(string): + from lib.debugMessage import debugMessage from lib.errorMessage import errorMessage + pe_dir = '' try: PE_axis = abs(int(string)) if PE_axis > 2: errorMessage('Phase encode axis must be either 0, 1 or 2') reverse = (string[0] == '-') # Allow -0 - return ( PE_axis, reverse ) + pe_dir = ( PE_axis, reverse ) except: string = string.lower() if string == 'lr': - return ( 0, False ) + pe_dir = ( 0, False ) elif string == 'rl': - return ( 0, True ) + pe_dir = ( 0, True ) elif string == 'pa': - return ( 1, False ) + pe_dir = ( 1, False ) elif string == 'ap': - return ( 1, True ) + pe_dir = ( 1, True ) elif string == 'is': - return ( 2, False ) + pe_dir = ( 2, False ) elif string == 'si': - return ( 2, True ) + pe_dir = ( 2, True ) elif string == 'i': - return ( 0, False ) + pe_dir = ( 0, False ) elif string == 'i-': - return ( 0, True ) + pe_dir = ( 0, True ) elif string == 'j': - return ( 1, False ) + pe_dir = ( 1, False ) elif string == 'j-': - return ( 1, True ) + pe_dir = ( 1, True ) elif string == 'k': - return ( 2, False ) + pe_dir = ( 2, False ) elif string == 'k-': - return ( 2, True ) + pe_dir = ( 2, True ) else: errorMessage('Unrecognized phase encode direction specifier: ' + string) - + debugMessage(string + ' -> ' + str(pe_dir) + ' (axis, reversed)') + return pe_dir diff --git a/scripts/lib/getTempImagePath.py b/scripts/lib/getTempImagePath.py index 1907a853f0..a573b43fd4 100644 --- a/scripts/lib/getTempImagePath.py +++ b/scripts/lib/getTempImagePath.py @@ -1,6 +1,7 @@ def getTempImagePath(suffix): import os, random, string - from readMRtrixConfSetting import readMRtrixConfSetting + from lib.debugMessage import debugMessage + from lib.readMRtrixConfSetting import readMRtrixConfSetting dir_path = readMRtrixConfSetting('TmpFileDir') if not dir_path dir_path = '.' @@ -13,5 +14,6 @@ def getTempImagePath(suffix): while os.path.exists(full_path) random_string = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(6)) full_path = os.path.join(dir_path, prefix + random_string + '.' + suffix + debugMessage(full_path) return full_path diff --git a/scripts/lib/getUserPath.py b/scripts/lib/getUserPath.py index 07d7289dce..eb47ba5cff 100644 --- a/scripts/lib/getUserPath.py +++ b/scripts/lib/getUserPath.py @@ -1,10 +1,12 @@ def getUserPath(filename, is_command): import lib.app, os + from lib.debugMessage import debugMessage # Places quotation marks around the full path, to handle cases where the user-specified path / file name contains spaces # However, this should only occur in situations where the output is being interpreted as part of a full command string; # if the expected output is a stand-alone string, the quotation marks should be omitted wrapper='' if is_command and (filename.count(' ') or lib.app.workingDir.count(' ')): wrapper='\"' - return wrapper + os.path.abspath(os.path.join(lib.app.workingDir, filename)) + wrapper - + path = wrapper + os.path.abspath(os.path.join(lib.app.workingDir, filename)) + wrapper + debugMessage(path) + return path diff --git a/scripts/lib/imagesMatch.py b/scripts/lib/imagesMatch.py index 71982f8e2f..0324e9750d 100644 --- a/scripts/lib/imagesMatch.py +++ b/scripts/lib/imagesMatch.py @@ -1,10 +1,13 @@ def imagesMatch(image_one, image_two): import math + from lib.debugMessage import debugMessage from lib.getHeaderInfo import getHeaderInfo + debug_prefix = '\'' + image_one + '\' \'' + image_two + '\'' # Image dimensions one_dim = [ int(i) for i in getHeaderInfo(image_one, 'size').split() ] two_dim = [ int(i) for i in getHeaderInfo(image_two, 'size').split() ] if not one_dim == two_dim: + debugMessage(debug_prefix + ' dimension mismatch (' + str(one_dim) + ' ' + str(two_dim) + ')') return False # Voxel size one_spacing = [ float(f) for f in getHeaderInfo(image_one, 'vox').split() ] @@ -12,13 +15,16 @@ def imagesMatch(image_one, image_two): for one, two in zip(one_spacing, two_spacing): if one and two and not math.isnan(one) and not math.isnan(two): if (abs(two-one) / (0.5*(one+two))) > 1e-04: + debugMessage(debug_prefix + ' voxel size mismatch (' + str(one_spacing) + ' ' + str(two_spacing) + ')') return False # Image transform one_transform = [ float(f) for f in getHeaderInfo(image_one, 'transform').replace('\n', ' ').replace(',', ' ').split() ] two_transform = [ float(f) for f in getHeaderInfo(image_two, 'transform').replace('\n', ' ').replace(',', ' ').split() ] for one, two in zip(one_transform, two_transform): if abs(one-two) > 1e-4: + debugMessage(debug_prefix + ' transform mismatch (' + str(one_transform) + ' ' + str(two_transform) + ')') return False # Everything matches! + debugMessage(debug_prefix + ' image match') return True diff --git a/scripts/lib/isWindows.py b/scripts/lib/isWindows.py index 5fbdae5134..4498e8dea6 100644 --- a/scripts/lib/isWindows.py +++ b/scripts/lib/isWindows.py @@ -1,5 +1,12 @@ -def isWindows(): - import platform - system = platform.system().lower() - return system.startswith('mingw') or system.startswith('msys') or system.startswith('windows') +_value = None +def isWindows(): + import platform + from lib.debugMessage import debugMessage + global _value + if _value is not None: + return _value + system = platform.system().lower() + _value = system.startswith('mingw') or system.startswith('msys') or system.startswith('windows') + debugMessage('System = ' + system + ' is Windows? ' + str(_value)) + return _value \ No newline at end of file diff --git a/scripts/lib/printMessage.py b/scripts/lib/printMessage.py index f121a415bf..573469781c 100644 --- a/scripts/lib/printMessage.py +++ b/scripts/lib/printMessage.py @@ -1,6 +1,5 @@ def printMessage(message): import lib.app, os, sys if lib.app.verbosity: - sys.stdout.write(os.path.basename(sys.argv[0]) + ': ' + lib.app.colourPrint + message + lib.app.colourClear + '\n') - sys.stdout.flush() + sys.stderr.write(os.path.basename(sys.argv[0]) + ': ' + lib.app.colourPrint + message + lib.app.colourClear + '\n') diff --git a/scripts/lib/readMRtrixConfSetting.py b/scripts/lib/readMRtrixConfSetting.py index 52c288d883..aa0319106e 100644 --- a/scripts/lib/readMRtrixConfSetting.py +++ b/scripts/lib/readMRtrixConfSetting.py @@ -1,5 +1,7 @@ # TODO Add compatibility with Windows (config files are in different locations) def readMRtrixConfSetting(name): + from lib.debugMessage import debugMessage + debug_prefix = 'Key \'' + name + '\': ' # Function definition: looking for key in whichever of the two files is open def findKey(f): @@ -14,13 +16,21 @@ def findKey(f): f = open ('.mrtrix.conf', 'r') value = findKey(f) if value: + debugMessage(debug_prefix + 'Value ' + value + ' found in user config file') return value except IOError: + debugMessage(debug_prefix + 'Could not scan user config file') pass try: f = open ('/etc/mrtrix.conf', 'r') + value = findKey(f) + if value: + debugMessage(debug_prefix + 'Value ' + value + ' found in system config file') + return value except IOError: + debugMessage(debug_prefix + 'Could not scan system config file') return '' - return findKey(f) + debugMessage(debug_prefix + 'Not found in any config file') + return '' diff --git a/scripts/lib/runCommand.py b/scripts/lib/runCommand.py index 952e98b329..2d7fb1eff3 100644 --- a/scripts/lib/runCommand.py +++ b/scripts/lib/runCommand.py @@ -3,6 +3,7 @@ def runCommand(cmd, exitOnError=True): import lib.app, os, subprocess, sys + from lib.debugMessage import debugMessage from lib.errorMessage import errorMessage from lib.isWindows import isWindows from lib.printMessage import printMessage @@ -23,12 +24,10 @@ def runCommand(cmd, exitOnError=True): if lib.app.lastFile in cmd: lib.app.lastFile = '' if lib.app.verbosity: - sys.stdout.write('Skipping command: ' + cmd + '\n') - sys.stdout.flush() + sys.stderr.write(lib.app.colourConsole + 'Skipping command:' + lib.app.colourClear + ' ' + cmd + '\n') return # Vectorise the command string, preserving anything encased within quotation marks - # This will eventually allow the use of subprocess rather than os.system() # TODO Use shlex.split()? quotation_split = cmd.split('\"') if not len(quotation_split)%2: @@ -71,15 +70,15 @@ def runCommand(cmd, exitOnError=True): if is_mrtrix_binary: if lib.app.mrtrixNThreads: new_cmdsplit.extend(lib.app.mrtrixNThreads.strip().split()) - if lib.app.mrtrixQuiet: - new_cmdsplit.append(lib.app.mrtrixQuiet.strip()) + if lib.app.mrtrixVerbosity: + new_cmdsplit.append(lib.app.mrtrixVerbosity.strip()) next_is_binary = True new_cmdsplit.append(item) if is_mrtrix_binary: if lib.app.mrtrixNThreads: new_cmdsplit.extend(lib.app.mrtrixNThreads.strip().split()) - if lib.app.mrtrixQuiet: - new_cmdsplit.append(lib.app.mrtrixQuiet.strip()) + if lib.app.mrtrixVerbosity: + new_cmdsplit.append(lib.app.mrtrixVerbosity.strip()) cmdsplit = new_cmdsplit # If the piping symbol appears anywhere, we need to split this into multiple commands and execute them separately @@ -93,8 +92,9 @@ def runCommand(cmd, exitOnError=True): cmdstack.append(cmdsplit[prev:]) if lib.app.verbosity: - sys.stdout.write(lib.app.colourConsole + 'Command:' + lib.app.colourClear + ' ' + cmd + '\n') - sys.stdout.flush() + sys.stderr.write(lib.app.colourConsole + 'Command:' + lib.app.colourClear + ' ' + cmd + '\n') + + debugMessage('To execute: ' + str(cmdstack)) # Execute all processes processes = [ ] @@ -125,14 +125,16 @@ def runCommand(cmd, exitOnError=True): stderrdata += line if not line and process.poll() != None: break - (stdoutdata, nullstderrdata) = process.communicate() - stdoutdata = stdoutdata.decode('utf-8') else: process.wait() - (stdoutdata, stderrdata) = process.communicate() - stdoutdata = stdoutdata.decode('utf-8') - stderrdata = stderrdata.decode('utf-8') + # Let all commands complete before grabbing stdout data; querying the stdout data + # immediately after command completion can intermittently prevent the data from + # getting to the following command (e.g. MRtrix piping) + for process in processes: + (stdoutdata, stderrdata) = process.communicate() + stdoutdata = stdoutdata.decode('utf-8') + stderrdata = stderrdata.decode('utf-8') return_stdout += stdoutdata + '\n' return_stderr += stderrdata + '\n' if process.returncode: diff --git a/scripts/lib/warnMessage.py b/scripts/lib/warnMessage.py index fbea4f8d8d..356c35a638 100644 --- a/scripts/lib/warnMessage.py +++ b/scripts/lib/warnMessage.py @@ -1,5 +1,4 @@ def warnMessage(message): import lib.app, os, sys - sys.stdout.write(os.path.basename(sys.argv[0]) + ': ' + lib.app.colourWarn + '[WARNING] ' + message + lib.app.colourClear + '\n') - sys.stdout.flush() + sys.stderr.write(os.path.basename(sys.argv[0]) + ': ' + lib.app.colourWarn + '[WARNING] ' + message + lib.app.colourClear + '\n') From 3d54edebee1c47e9e686ec0ba5c85e690ac7cda3 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Tue, 16 Aug 2016 15:27:30 +1000 Subject: [PATCH 072/723] Finished porting fixelreorient to new fixel format. Made a few improvements to fixel helper functions --- cmd/fixelreorient.cpp | 69 +++++++++++++++------- lib/fixel_format/helpers.h | 60 ++++++++++++++++--- lib/registration/warp/utils.h | 53 ----------------- src/gui/mrview/tool/vector/fixelfolder.cpp | 38 +++++------- src/gui/mrview/tool/vector/fixelfolder.h | 4 +- src/gui/mrview/tool/vector/sparsefixel.h | 6 +- src/gui/mrview/tool/vector/vector.cpp | 10 ++-- testing/data | 2 +- 8 files changed, 125 insertions(+), 117 deletions(-) delete mode 100644 lib/registration/warp/utils.h diff --git a/cmd/fixelreorient.cpp b/cmd/fixelreorient.cpp index 0fb85bb8fd..d527db87a5 100644 --- a/cmd/fixelreorient.cpp +++ b/cmd/fixelreorient.cpp @@ -21,6 +21,7 @@ #include "sparse/fixel_metric.h" #include "sparse/image.h" #include "adapter/jacobian.h" +#include "registration/warp/helpers.h" using namespace MR; using namespace App; @@ -33,45 +34,71 @@ void usage () AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; DESCRIPTION - + "Inplace reorientation of fixel directions using the local affine transformation (Jacobian matrix) at each voxel within an input warp."; + + "Reorient fixel directions. 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."; ARGUMENTS + Argument ("fixel_in", "the fixel folder").type_text () + 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 ("dir_out", "the output fixel folder. If the the input and output folders are the same the directions file will " - "replaced. If a new folder is supplied then all fixel data will be copied to the new folder.").type_text (); + + Argument ("fixel_out", "the output fixel folder. If the the input and output folders are the same, the existing directions file will " + "be replaced (providing the --force option is supplied). If a new folder is supplied then all " + "fixel data will be copied to the new folder.").type_text (); } void run () { - const auto fixel_folder = argument[0]; - FixelFormat::check_fixel_folder (fixel_folder); + std::string input_fixel_folder = argument[0]; + FixelFormat::check_fixel_folder (input_fixel_folder); - Header warp_header = Header::open(argument[1]); + auto input_index_image = FixelFormat::find_index_header (input_fixel_folder).get_image (); - const auto out_fixel_folder = argument[2]; - auto index_image = FixelFormat::find_index_header (fixel_folder).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()); - check_dimensions (index_image, warp_header, 0, 3); + std::string output_fixel_folder = argument[2]; + FixelFormat::check_fixel_folder (output_fixel_folder, true); - if (warp_header.ndim() != 4) - throw Exception ("The input deformation field image must be a 4D file."); - if (warp_header.size(3) != 3) - Adapter::Jacobian > jacobian (warp_header.get_image()); + // scratch buffer so inplace reorientation can be performed if desired + Image input_directions_image; + std::string output_directions_filename; + { + auto tmp = FixelFormat::find_directions_header (input_fixel_folder, input_index_image).get_image(); + input_directions_image = Image::scratch(tmp); + threaded_copy (tmp, input_directions_image); + output_directions_filename = Path::basename(tmp.name()); + } - Sparse::Image output_fixel (argument[2], input_header); + auto output_directions_image = Image::create (Path::join(output_fixel_folder, 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) { + input_index_image.index(3) = 0; + uint32_t num_fixels_in_voxel = input_index_image.value(); + if (num_fixels_in_voxel) { + input_index_image.index(3) = 1; + uint32_t index = input_index_image.value(); + Eigen::Matrix transform = jacobian.value().inverse(); + for (size_t 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 * input_directions_image.row(1); + } + } + } - for (auto i = Loop ("reorienting fixel directions", input_fixel) (input_fixel, jacobian, output_fixel); i; ++i) { - output_fixel.value().set_size (input_fixel.value().size()); - for (size_t f = 0; f != input_fixel.value().size(); ++f) { - output_fixel.value()[f] = input_fixel.value()[f]; - Eigen::Vector3f subject_fixel_direction = jacobian.value().inverse() * input_fixel.value()[f].dir; - subject_fixel_direction.normalize(); - output_fixel.value()[f].dir = subject_fixel_direction; + if (output_fixel_folder != input_fixel_folder) { + auto output_index_image = Image::create (Path::join (output_fixel_folder, Path::basename (input_index_image.name())), input_index_image); + threaded_copy (input_index_image, output_index_image); + for (auto& header : FixelFormat::find_data_headers (input_fixel_folder, input_index_image)) { + auto input = header.get_image(); + auto output = Image::create (Path::join (output_fixel_folder, Path::basename (input.name())), input); + threaded_copy (input, output); } } } diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index 1574be3b08..2368d64d08 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -49,6 +49,12 @@ namespace MR return in.ndim () == 3 && in.size (2) == 1; } + inline bool is_directions_image (const Header& in) + { + std::string basename (Path::basename (in.name())); + return in.ndim () == 3 && in.size (1) == 3 && in.size (2) == 1 && (basename.substr(0, basename.find_last_of(".")) == "directions"); + } + inline void check_data_image (const Header& in) { @@ -57,12 +63,12 @@ namespace MR } - inline bool fixels_match (const Header& index_h, const Header& data_h) + inline bool fixels_match (const Header& index_header, const Header& data_header) { bool fixels_match (false); - if (is_index_image (index_h)) { - fixels_match = std::stoul(index_h.keyval ().at (n_fixels_key)) == (unsigned long)data_h.size (0); + if (is_index_image (index_header)) { + fixels_match = std::stoul(index_header.keyval ().at (n_fixels_key)) == (unsigned long)data_header.size (0); } return fixels_match; @@ -85,7 +91,7 @@ namespace MR if (!(exists = Path::exists (path))) { if (create_if_missing) File::mkdir (path); - else throw Exception ("Fixel directory " + str(path) + " does not exist"); + else throw Exception ("Fixel directory (" + str(path) + ") does not exist"); } else if (!Path::is_dir (path)) throw Exception (str(path) + " is not a directory"); @@ -129,15 +135,53 @@ namespace MR while ((fname = dir_walker.read_name ()).size ()) { auto full_path = Path::join (fixel_folder_path, fname); Header H; - if (Path::has_suffix (fname, FixelFormat::supported_fixel_formats) - && is_data_image (H = Header::open (full_path)) - && fixels_match (index_header, H)) { - data_headers.emplace_back (std::move (H)); + if (Path::has_suffix (fname, FixelFormat::supported_fixel_formats) && is_data_image (H = Header::open (full_path))) { + if (fixels_match (index_header, H)) { + if (!is_directions_image (H)) + data_headers.emplace_back (std::move (H)); + } else { + WARN ("fixel data file (" + fname + ") does not contain the same number of elements as fixels in the index file" ); + } } } return data_headers; } + + + inline Header find_directions_header (const std::string &fixel_folder_path, const Header &index_header) + { + + bool directions_found (false); + Header header; + check_fixel_folder (fixel_folder_path); + + auto dir_walker = Path::Dir (fixel_folder_path); + std::string fname; + while ((fname = dir_walker.read_name ()).size ()) { + Header tmp_header; + auto full_path = Path::join (fixel_folder_path, fname); + if (Path::has_suffix (fname, FixelFormat::supported_fixel_formats) + && is_directions_image (tmp_header = Header::open (full_path))) { + if (is_directions_image (tmp_header)) { + if (fixels_match (index_header, tmp_header)) { + if (directions_found == true) + throw Exception ("multiple directions files found in fixel image folder: " + fixel_folder_path); + directions_found = true; + header = std::move(tmp_header); + } else { + WARN ("fixel directions file (" + fname + ") does not contain the same number of elements as fixels in the index file" ); + } + } + } + } + + if (!directions_found) + throw InvalidFixelDirectoryException ("Could not find directions image in directory " + fixel_folder_path); + + return header; + } + } } diff --git a/lib/registration/warp/utils.h b/lib/registration/warp/utils.h deleted file mode 100644 index af8708bbc1..0000000000 --- a/lib/registration/warp/utils.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - -#ifndef __registration_warp_utils_h__ -#define __registration_warp_utils_h__ - -namespace MR -{ - namespace Registration - { - - namespace Warp - { - - template - transform_type parse_linear_transform (InputWarpType& input_warps, std::string name) { - transform_type linear; - const auto it = input_warps.keyval().find (name); - if (it != input_warps.keyval().end()) { - const auto lines = split_lines (it->second); - if (lines.size() != 3) - throw Exception ("linear transform in initialisation syn warps image header does not contain 3 rows"); - for (size_t row = 0; row < 3; ++row) { - const auto values = split (lines[row], " ", true); - if (values.size() != 4) - throw Exception ("linear transform in initialisation syn warps image header does not contain 4 columns"); - for (size_t col = 0; col < 4; ++col) - linear (row, col) = std::stod (values[col]); - } - } else { - throw Exception ("no linear transform found in initialisation syn warps image header"); - } - - return linear; - } - - } - } -} - -#endif diff --git a/src/gui/mrview/tool/vector/fixelfolder.cpp b/src/gui/mrview/tool/vector/fixelfolder.cpp index 4d728a2aac..5bed921777 100644 --- a/src/gui/mrview/tool/vector/fixelfolder.cpp +++ b/src/gui/mrview/tool/vector/fixelfolder.cpp @@ -57,35 +57,25 @@ namespace MR } } - auto data_headers = FixelFormat::find_data_headers (Path::dirname (fixel_data->name ()), *fixel_data); - - // Load fixel data direction images - // Currently only assuming one direction file exists - for (Header& header : data_headers) { - - if (header.size (1) != 3) continue; - auto data_image = header.get_image ().with_direct_io (); - data_image.index (1) = 0; - for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { - fixel_data->index (3) = 0; - const size_t nfixels = fixel_data->value (); - fixel_data->index (3) = 1; - const size_t offset = fixel_data->value (); - for (size_t f = 0; f < nfixels; ++f) { - data_image.index (0) = offset + f; - dir_buffer_store.emplace_back (data_image.row (1)); - } + // Load fixel direction images + auto directions_image = FixelFormat::find_directions_header (Path::dirname (fixel_data->name ()), *fixel_data).get_image ().with_direct_io (); + std::cout << directions_image.name() << std::endl; + directions_image.index (1) = 0; + for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { + fixel_data->index (3) = 0; + const size_t nfixels = fixel_data->value (); + fixel_data->index (3) = 1; + const size_t offset = fixel_data->value (); + for (size_t f = 0; f < nfixels; ++f) { + directions_image.index (0) = offset + f; + dir_buffer_store.emplace_back (directions_image.row (1)); } - - break; } - if (!dir_buffer_store.size ()) - throw InvalidImageException ("Fixel index image " + fixel_data->name () + " has no associated directions file"); - - // Load fixel data value images + // Load fixel data images + auto data_headers = FixelFormat::find_data_headers (Path::dirname (fixel_data->name ()), *fixel_data); for (auto& header : data_headers) { if (header.size (1) != 1) continue; diff --git a/src/gui/mrview/tool/vector/fixelfolder.h b/src/gui/mrview/tool/vector/fixelfolder.h index bf5e3b79b9..4fcd427324 100644 --- a/src/gui/mrview/tool/vector/fixelfolder.h +++ b/src/gui/mrview/tool/vector/fixelfolder.h @@ -31,8 +31,8 @@ namespace MR FixelFolder (const std::string& filename, Vector& fixel_tool) : FixelType (FixelFormat::find_index_header (Path::dirname (filename)).name (), fixel_tool) { - value_types = {"Unity"}; - colour_types = {"Direction"}; + value_types = {"unity"}; + colour_types = {"direction"}; fixel_data.reset (new FixelIndexImageType (header.get_image ())); load_image (filename); diff --git a/src/gui/mrview/tool/vector/sparsefixel.h b/src/gui/mrview/tool/vector/sparsefixel.h index 9f012855c6..56ee1eb628 100644 --- a/src/gui/mrview/tool/vector/sparsefixel.h +++ b/src/gui/mrview/tool/vector/sparsefixel.h @@ -31,9 +31,9 @@ namespace MR SparseFixel (const std::string& filename, Vector& fixel_tool) : FixelType (filename, fixel_tool) { - value_types = {"Unity", "Fixel size", "Associated value"}; - colour_types = {"Direction", "Fixel size", "Associated value"}; - threshold_types = {"Fixel size", "Associated value"}; + value_types = {"unity", "fixel size", "associated value"}; + colour_types = {"direction", "fixel size", "associated value"}; + threshold_types = {"fixel size", "associated value"}; fixel_values[value_types[1]]; fixel_values[value_types[2]]; diff --git a/src/gui/mrview/tool/vector/vector.cpp b/src/gui/mrview/tool/vector/vector.cpp index f017da1afe..eadc80f8b0 100644 --- a/src/gui/mrview/tool/vector/vector.cpp +++ b/src/gui/mrview/tool/vector/vector.cpp @@ -147,8 +147,8 @@ namespace MR // Colouring colour_combobox = new ComboBoxWithErrorMsg (0, " (variable) "); hlayout->addWidget (new QLabel ("colour by ")); - colour_combobox->addItem ("Direction"); - colour_combobox->addItem ("Value"); + colour_combobox->addItem ("direction"); + colour_combobox->addItem ("value"); hlayout->addWidget (colour_combobox, 0); connect (colour_combobox, SIGNAL (activated(int)), this, SLOT (colour_changed_slot(int))); @@ -205,9 +205,9 @@ namespace MR hlayout->addWidget (new QLabel ("scale by ")); length_combobox = new ComboBoxWithErrorMsg (0, " (variable) "); - length_combobox->addItem ("Unity"); - length_combobox->addItem ("Fixel size"); - length_combobox->addItem ("Associated value"); + length_combobox->addItem ("unity"); + length_combobox->addItem ("fixel size"); + length_combobox->addItem ("associated value"); hlayout->addWidget (length_combobox, 0); connect (length_combobox, SIGNAL (activated(int)), this, SLOT (length_type_slot(int))); diff --git a/testing/data b/testing/data index f3d39886d7..99cefe043c 160000 --- a/testing/data +++ b/testing/data @@ -1 +1 @@ -Subproject commit f3d39886d7c02e8b9017bb4ced3babca23884e68 +Subproject commit 99cefe043c0f2ab6b91abf9858a153e061f8a0bd From 6bd5b349877bf563353b863a39d3b28215fcf2ea Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 17 Aug 2016 08:55:43 +1000 Subject: [PATCH 073/723] update testing --- testing/data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/data b/testing/data index 99cefe043c..d8ada305cb 160000 --- a/testing/data +++ b/testing/data @@ -1 +1 @@ -Subproject commit 99cefe043c0f2ab6b91abf9858a153e061f8a0bd +Subproject commit d8ada305cbc1e7b02139b1321131bb3dce3d0204 From 3b60d53f039ac4ed22d94eb2778d637413e6026b Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 17 Aug 2016 15:02:05 +1000 Subject: [PATCH 074/723] moved warp helper to src --- {lib => src}/registration/warp/helpers.h | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {lib => src}/registration/warp/helpers.h (100%) diff --git a/lib/registration/warp/helpers.h b/src/registration/warp/helpers.h similarity index 100% rename from lib/registration/warp/helpers.h rename to src/registration/warp/helpers.h From 6cb6033605d64cbf4c6eb093267f7f94f146fabd Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 17 Aug 2016 15:10:02 +1000 Subject: [PATCH 075/723] fod2fixel: fix nii index/directions output --- cmd/fod2fixel.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/fod2fixel.cpp b/cmd/fod2fixel.cpp index 54802ab75a..3252558891 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -306,14 +306,16 @@ void run () Math::SH::check (H); auto fod_data = H.get_image(); - Segmented_FOD_receiver receiver (H); + const bool dir_as_peak = get_options ("dirpeak").size() ? true : false; + + Segmented_FOD_receiver receiver (H, dir_as_peak); auto& fixel_folder_path = argument[1]; receiver.set_fixel_folder_output (fixel_folder_path); std::string file_extension (".mif"); if (get_options ("nii").size()) - file_extension = "nii"; + file_extension = ".nii"; static const std::string default_index_filename ("index" + file_extension); static const std::string default_directions_filename ("directions" + file_extension); From 8bb98eeb341b79483971aa1fa95931365ac06398 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 17 Aug 2016 16:19:02 +1000 Subject: [PATCH 076/723] fmls removed min_peak_amp --- cmd/fod2fixel.cpp | 4 ++-- src/dwi/fmls.cpp | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/fod2fixel.cpp b/cmd/fod2fixel.cpp index 3252558891..75971b25f0 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -143,9 +143,9 @@ class Segmented_FOD_receiver { for (const FOD_lobe& lobe : in) { if (asdf) - this->emplace_back (lobe.get_peak_dir(0), lobe.get_integral(), lobe.get_max_peak_value()); + 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(), lobe.get_integral(), lobe.get_max_peak_value()); + this->emplace_back (lobe.get_mean_dir().cast(), lobe.get_integral(), lobe.get_max_peak_value()); } } }; diff --git a/src/dwi/fmls.cpp b/src/dwi/fmls.cpp index 02853ca862..312cbbff12 100644 --- a/src/dwi/fmls.cpp +++ b/src/dwi/fmls.cpp @@ -284,7 +284,6 @@ namespace MR { } } - const default_type min_peak_amp = ratio_to_negative_lobe_mean_peak * (mean_neg_peak / default_type(neg_lobe_count)); const default_type min_integral = ratio_to_negative_lobe_integral * max_neg_integral; for (auto i = out.begin(); i != out.end();) { // Empty increment From 35145c6ac5a852810643b29a0a21f8657143c76e Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 17 Aug 2016 16:19:42 +1000 Subject: [PATCH 077/723] removed fixellog --- cmd/fixellog.cpp | 60 ------------------------------------------------ 1 file changed, 60 deletions(-) delete mode 100644 cmd/fixellog.cpp diff --git a/cmd/fixellog.cpp b/cmd/fixellog.cpp deleted file mode 100644 index 745a1415b1..0000000000 --- a/cmd/fixellog.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - - -#include "command.h" -#include "progressbar.h" -#include "algo/loop.h" - -#include "image.h" - -#include "sparse/fixel_metric.h" -#include "sparse/keys.h" -#include "sparse/image.h" - -using namespace MR; -using namespace App; - -using Sparse::FixelMetric; - -void usage () -{ - AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; - - DESCRIPTION - + "compute the natural logarithm of all values in a fixel image"; - - ARGUMENTS - + Argument ("input", "the input fixel image.").type_image_in () - + Argument ("output", "the output fixel image.").type_image_out (); -} - - -void run () -{ - auto header = Header::open (argument[0]); - Sparse::Image input (argument[0]); - - Sparse::Image output (argument[1], header); - - for (auto i = Loop ("computing log", input) (input, output); i; ++i) { - output.value().set_size (input.value().size()); - for (size_t fixel = 0; fixel != input.value().size(); ++fixel) { - output.value()[fixel] = input.value()[fixel]; - output.value()[fixel].value = std::log (input.value()[fixel].value); - } - } -} - From 9c5753eaef4bd581002d046a9eb70c5b91198a2c Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 17 Aug 2016 16:19:58 +1000 Subject: [PATCH 078/723] remove fixelcalc --- cmd/fixelcalc.cpp | 102 ---------------------------------------------- 1 file changed, 102 deletions(-) delete mode 100644 cmd/fixelcalc.cpp diff --git a/cmd/fixelcalc.cpp b/cmd/fixelcalc.cpp deleted file mode 100644 index 43044204fd..0000000000 --- a/cmd/fixelcalc.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - - -#include "command.h" -#include "progressbar.h" -#include "algo/loop.h" - -#include "image.h" - -#include "sparse/fixel_metric.h" -#include "sparse/keys.h" -#include "sparse/image.h" - -using namespace MR; -using namespace App; - -using Sparse::FixelMetric; - -const char* operation[] = { "add", "sub", "mult", "div", NULL }; - -void usage () -{ - AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; - - DESCRIPTION - + "Perform basic calculations (add, subtract, multiply, divide) between two fixel images"; - - ARGUMENTS - + Argument ("input1", "the input fixel image.").type_image_in () - + Argument ("operation", "the type of operation to be applied (either add, sub, mult or divide)").type_choice (operation) - + Argument ("input2", "the input fixel image.").type_image_in () - + Argument ("output", "the output fixel image.").type_image_out (); -} - - - -float add (float a, float b) { return a + b; } -float subtract (float a, float b) { return a - b; } -float multiply (float a, float b) { return a * b; } -float divide (float a, float b) { return a / b; } - - -void run () -{ - auto header = Header::open (argument[0]); - Sparse::Image input1 (argument[0]); - Sparse::Image input2 (argument[2]); - - check_dimensions (input1, input2); - - Sparse::Image output (argument[3], header); - - const size_t operation = argument[1]; - - std::string message; - float (*op)(float, float) = NULL; - switch (operation) { - case 0: - message = "adding fixel images"; - op = &add; - break; - case 1: - message = "subtracting fixel images"; - op = &subtract; - break; - case 2: - message = "multiplying fixel images"; - op = &multiply; - break; - case 3: - message = "dividing fixel images"; - op = ÷ - break; - default: - break; - } - - - for (auto i = Loop (message, input1) (input1, input2, output); i; ++i) { - if (input1.value().size() != input2.value().size()) - throw Exception ("the fixel images do not have corresponding fixels in all voxels"); - output.value().set_size (input1.value().size()); - for (size_t fixel = 0; fixel != input1.value().size(); ++fixel) { - output.value()[fixel] = input1.value()[fixel]; - output.value()[fixel].value = (*op)(input1.value()[fixel].value, input2.value()[fixel].value); - } - } -} - From e62ad040f0e1f1ca38d2d5fc394551a79d17dc22 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 17 Aug 2016 16:29:07 +1000 Subject: [PATCH 079/723] removed fixelhistogram since fixel data files can now be fed into mrstats --- cmd/fixelhistogram.cpp | 108 ----------------------------------------- 1 file changed, 108 deletions(-) delete mode 100644 cmd/fixelhistogram.cpp diff --git a/cmd/fixelhistogram.cpp b/cmd/fixelhistogram.cpp deleted file mode 100644 index adf21872c0..0000000000 --- a/cmd/fixelhistogram.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - - -#include "command.h" -#include "algo/histogram.h" -#include "algo/loop.h" - -#include "image.h" - -#include "sparse/fixel_metric.h" -#include "sparse/keys.h" -#include "sparse/image.h" - - -using namespace MR; -using namespace App; - -using Sparse::FixelMetric; - -void usage () -{ - AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; - - DESCRIPTION - + "Generate a histogram of fixel values."; - - ARGUMENTS - + Argument ("input", "the input fixel image.").type_image_in (); - - OPTIONS - + Algo::Histogram::Options; -} - - -void run () -{ - Sparse::Image input (argument[0]); - - auto opt = get_options("mask"); - std::unique_ptr > mask_ptr; - if (opt.size()) { - mask_ptr.reset (new Sparse::Image (opt[0][0])); - check_dimensions (input, *mask_ptr); - } - - File::OFStream output (argument[1]); - - size_t nbins = get_option_value ("bins", 0); - Algo::Histogram::Calibrator calibrator (nbins, get_options ("ignorezero").size()); - - opt = get_options ("template"); - if (opt.size()) { - calibrator.from_file (opt[0][0]); - } else { - for (auto i = Loop (input) (input); i; ++i) { - if (mask_ptr) { - assign_pos_of (input).to (*mask_ptr); - if (input.value().size() != mask_ptr->value().size()) - throw Exception ("the input fixel image and mask image to not have corrresponding fixels"); - } - for (size_t fixel = 0; fixel != input.value().size(); ++fixel) { - if (mask_ptr) { - if (mask_ptr->value()[fixel].value > 0.5) - calibrator (input.value()[fixel].value); - } else { - calibrator (input.value()[fixel].value); - } - } - } - calibrator.finalize (1, false); - } - - Algo::Histogram::Data histogram (calibrator); - - for (auto i = Loop (input) (input); i; ++i) { - if (mask_ptr) - assign_pos_of (input).to (*mask_ptr); - - for (size_t fixel = 0; fixel != input.value().size(); ++fixel) { - if (mask_ptr) { - if (mask_ptr->value()[fixel].value > 0.5) - histogram (input.value()[fixel].value); - } else { - histogram (input.value()[fixel].value); - } - } - } - - for (size_t i = 0; i != nbins; ++i) - output << (calibrator.get_min() + ((i+0.5) * calibrator.get_bin_width())) << ","; - output << "\n"; - for (size_t i = 0; i != nbins; ++i) - output << histogram[i] << ","; - output << "\n"; -} From fe04fe4bfe5a887fb5dba9e468628eb6bf4b65e7 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 17 Aug 2016 16:29:55 +1000 Subject: [PATCH 080/723] removed fixelstats since fixel data files can now be fed into mrstats --- cmd/fixelstats.cpp | 85 ---------------------------------------------- 1 file changed, 85 deletions(-) delete mode 100644 cmd/fixelstats.cpp diff --git a/cmd/fixelstats.cpp b/cmd/fixelstats.cpp deleted file mode 100644 index 1aae3e924f..0000000000 --- a/cmd/fixelstats.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - - -#include "command.h" -#include "algo/loop.h" - -#include "stats.h" -#include "image.h" - -#include "sparse/fixel_metric.h" -#include "sparse/keys.h" -#include "sparse/image.h" - - -using namespace MR; -using namespace App; - -using Sparse::FixelMetric; - -void usage () -{ - AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; - - DESCRIPTION - + "Compute fixel image statistics"; - - ARGUMENTS - + Argument ("input", "the input fixel image.").type_image_in (); - - OPTIONS - + Stats::Options; -} - - -void run () -{ - Sparse::Image input (argument[0]); - - auto opt = get_options("mask"); - std::unique_ptr > mask_ptr; - if (opt.size()) { - mask_ptr.reset (new Sparse::Image (opt[0][0])); - check_dimensions (input, *mask_ptr); - } - - std::vector fields; - opt = get_options ("output"); - for (size_t n = 0; n < opt.size(); ++n) - fields.push_back (opt[n][0]); - - Stats::Stats stats (false); - - for (auto i = Loop (input) (input); i; ++i) { - if (mask_ptr) { - assign_pos_of (input).to (*mask_ptr); - if (input.value().size() != mask_ptr->value().size()) - throw Exception ("the input fixel image and mask image to not have corrresponding fixels"); - } - - for (size_t fixel = 0; fixel != input.value().size(); ++fixel) { - if (mask_ptr) { - if (mask_ptr->value()[fixel].value > 0.5) - stats (input.value()[fixel].value); - } else { - stats (input.value()[fixel].value); - } - } - } - - Stats::print_header (false); - stats.print (input, fields); -} From 3872cb79ae9c398db4f19a2888ec0994c9450e95 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Fri, 19 Aug 2016 12:10:35 +1000 Subject: [PATCH 081/723] Added helpers to copy fixel files between folders --- lib/fixel_format/helpers.h | 60 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index 2368d64d08..ca7d2bea15 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -49,6 +49,7 @@ namespace MR return in.ndim () == 3 && in.size (2) == 1; } + inline bool is_directions_image (const Header& in) { std::string basename (Path::basename (in.name())); @@ -63,7 +64,8 @@ namespace MR } - inline bool fixels_match (const Header& index_header, const Header& data_header) + template + inline bool fixels_match (const IndexHeaderType& index_header, const DataHeaderType& data_header) { bool fixels_match (false); @@ -148,8 +150,8 @@ namespace MR return data_headers; } - - inline Header find_directions_header (const std::string &fixel_folder_path, const Header &index_header) + template + inline Header find_directions_header (const std::string &fixel_folder_path, const IndexHeaderType &index_header) { bool directions_found (false); @@ -182,6 +184,58 @@ namespace MR return header; } + + //! Copy a file from one fixel folder into another. If the output folder already contains the same file + //! then it is checked to make sure it's the same as the input (based on the number of fixels it contains) + inline void copy_fixel_file (const std::string& input_file_path, const std::string &output_folder, const bool check_existing_output = false) { + check_fixel_folder (output_folder, true); + std::string output_path = Path::join (output_folder, Path::basename (Path::basename(input_file_path))); + Header input_header = Header::open (input_file_path); + + // do not need to copy if the file already exists and has the same number of fixels + if (Path::exists (output_path) && check_existing_output) { + Header output_header = Header::open (output_path); + if (is_index_image (input_header)) { + if (input_header.keyval().at (n_fixels_key) != output_header.keyval().at (n_fixels_key)) + throw InvalidFixelDirectoryException ("Output fixel folder (" + output_folder + ") already contains fixel " + "index file with a different number of fixels to the generated output"); + } else if (is_data_image (input_header)) { + if (input_header.size(2) != output_header.size(2)) + throw InvalidFixelDirectoryException ("Output fixel folder (" + output_folder + ") already contains fixel " + "file " + input_header.name() + ") with a different number of fixels to the generated output"); + } else { + throw Exception ("Input file (" + input_header.name() + ") in fixel folder (" + output_folder + ") is not a valid fixel file"); + } + } else { + // create new directions file or overwrite with force + auto input_image = input_header.get_image(); + auto output_directions_image = Image::create (output_path, input_image); + threaded_copy (input_image, output_directions_image); + } + } + + //! Copy the index file from one fixel folder into another. When check_existing_output + //! is true, it will check if existing output exists and NOT overwrite as long as the number of fixels in the file is the same + inline void copy_index_file (const std::string &input_folder, const std::string &output_folder, const bool check_existing_output = false) { + Header input_header = FixelFormat::find_index_header (input_folder); + copy_fixel_file (input_header.name(), output_folder, check_existing_output); + } + + //! Copy the directions file from one fixel folder into another. When check_existing_output + //! is true, it will check if existing output exists and NOT overwrite as long as the number of fixels in the file is the same + inline void copy_directions_file (const std::string &input_folder, const std::string &output_folder, const bool check_existing_output = false) { + Header input_header = FixelFormat::find_directions_header (input_folder, FixelFormat::find_index_header (input_folder)); + copy_fixel_file (input_header.name(), output_folder, check_existing_output); + } + + + //! Copy all data files in a fixel folder into another folder. Data files do not include the index or directions file. When check_existing_output + //! is true, it will check if existing outputs exist and NOT overwrite as long as the number of fixels in the file is the same + inline void copy_all_data_files (const std::string &input_folder, const std::string &output_folder, const bool check_existing_output = false) { + for (auto& input_header : FixelFormat::find_data_headers (input_folder, FixelFormat::find_index_header (input_folder))) + copy_fixel_file (input_header.name(), output_folder, check_existing_output); + } + } } From a9a360b2976d2e1a3e076aa6b2a435cf828b0887 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Fri, 19 Aug 2016 12:11:19 +1000 Subject: [PATCH 082/723] Ported fixelcorrespondence to new fixel file format --- cmd/fixelcorrespondence.cpp | 65 +++++++++++++++++++++++-------------- cmd/fixelreorient.cpp | 12 ++----- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/cmd/fixelcorrespondence.cpp b/cmd/fixelcorrespondence.cpp index 4b82cabf63..2a63555bed 100644 --- a/cmd/fixelcorrespondence.cpp +++ b/cmd/fixelcorrespondence.cpp @@ -18,14 +18,12 @@ #include "progressbar.h" #include "algo/loop.h" #include "image.h" -#include "sparse/fixel_metric.h" -#include "sparse/image.h" +#include "fixel_format/helpers.h" +#include "fixel_format/keys.h" using namespace MR; using namespace App; -using Sparse::FixelMetric; - #define DEFAULT_ANGLE_THRESHOLD 30.0 void usage () @@ -33,13 +31,15 @@ void usage () AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; DESCRIPTION - + "Obtain angular correpondence by mapping subject fixels to a template fixel mask. " - "It is assumed that the subject image has already been spatially normalised and is aligned with the template."; + + "Obtain fixel-fixel correpondence between a subject fixel image and a template fixel mask." + "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."; ARGUMENTS - + Argument ("subject", "the input subject fixel image.").type_image_in () - + Argument ("template", "the input template fixel image.").type_image_in () - + Argument ("output", "the output fixel image.").type_image_out (); + + Argument ("subject_fixel_data", "the input subject fixel data file. This should be a file inside the fixel folder").type_image_in () + + Argument ("template_fixel_folder", "the input template fixel folder.").type_image_in () + + Argument ("output_fixel_folder", "the output fixel folder. Any existing index file in the output folder will be checked that it matches the expected output.").type_text() + + Argument ("output_fixel_file", "the output fixel data file. This will be placed in the output fixel folder").type_image_out (); OPTIONS + Option ("angle", "the max angle threshold for computing inter-subject fixel correspondence (Default: " + str(DEFAULT_ANGLE_THRESHOLD, 2) + " degrees)") @@ -49,37 +49,52 @@ void usage () 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)); - Sparse::Image subject_fixel (argument[0]); - - auto template_header = Header::open (argument[1]); - Sparse::Image template_fixel (argument[1]); + auto subject_index = FixelFormat::find_index_header (Path::dirname (argument[0])).get_image(); + auto subject_directions = FixelFormat::find_directions_header (Path::dirname (argument[0]), subject_index).get_image().with_direct_io(); + auto subject_data = Image::open (argument[0]); + FixelFormat::check_fixel_size (subject_index, subject_data); - check_dimensions (subject_fixel, template_fixel); + auto template_index = FixelFormat::find_index_header (argument[1]).get_image(); + auto template_directions = FixelFormat::find_directions_header (argument[1], template_index).get_image().with_direct_io(); - Sparse::Image output_fixel (argument[2], template_header); + check_dimensions (subject_index, template_index); + std::string output_fixel_folder = argument[2]; + FixelFormat::copy_index_file (Path::dirname (argument[1]), output_fixel_folder, true); + FixelFormat::copy_directions_file (Path::dirname (argument[1]), output_fixel_folder, true); - const float angular_threshold = get_option_value ("angle", DEFAULT_ANGLE_THRESHOLD); - const float angular_threshold_dp = cos (angular_threshold * (Math::pi/180.0)); + auto output_data = Image::create (Path::join (output_fixel_folder, argument[3]), subject_data); - for (auto i = Loop ("mapping subject fixels to template fixels", subject_fixel) (subject_fixel, template_fixel, output_fixel); i; ++i) { - output_fixel.value().set_size (template_fixel.value().size()); - for (size_t t = 0; t != template_fixel.value().size(); ++t) { - output_fixel.value()[t] = template_fixel.value()[t] ; + for (auto i = Loop ("mapping subject fixels to template fixels", template_index, 0, 3)(template_index, subject_index); i; ++i) { + template_index.index(3) = 0; + uint32_t nfixels_template = template_index.value(); + template_index.index(3) = 1; + uint32_t template_offset = template_index.value(); + for (size_t t = 0; t < nfixels_template; ++t) { float largest_dp = 0.0; int index_of_closest_fixel = -1; - for (size_t s = 0; s != subject_fixel.value().size(); ++s) { - float dp = std::abs (template_fixel.value()[t].dir.dot (subject_fixel.value()[s].dir)); + + subject_index.index(3) = 0; + uint32_t nfixels_subect = subject_index.value(); + subject_index.index(3) = 1; + uint32_t subject_offset = subject_index.value(); + template_directions.index(1) = template_offset + t; + for (size_t s = 0; s < nfixels_subect; ++s) { + subject_directions.index(1) = subject_offset + s; + float dp = std::abs (template_directions.row(1).dot (subject_directions.row(1))); if (dp > largest_dp) { largest_dp = dp; index_of_closest_fixel = s; } } if (largest_dp > angular_threshold_dp) { - output_fixel.value()[t].value = subject_fixel.value()[index_of_closest_fixel].value; + output_data.index(1) = template_offset + t; + subject_data.index(1) = subject_offset + index_of_closest_fixel; + output_data.value() = subject_data.value(); } } } - } diff --git a/cmd/fixelreorient.cpp b/cmd/fixelreorient.cpp index d527db87a5..7f9bd4b722 100644 --- a/cmd/fixelreorient.cpp +++ b/cmd/fixelreorient.cpp @@ -18,8 +18,6 @@ #include "progressbar.h" #include "algo/loop.h" #include "image.h" -#include "sparse/fixel_metric.h" -#include "sparse/image.h" #include "adapter/jacobian.h" #include "registration/warp/helpers.h" @@ -64,7 +62,6 @@ void run () std::string output_fixel_folder = argument[2]; FixelFormat::check_fixel_folder (output_fixel_folder, true); - // scratch buffer so inplace reorientation can be performed if desired Image input_directions_image; std::string output_directions_filename; @@ -93,13 +90,8 @@ void run () } if (output_fixel_folder != input_fixel_folder) { - auto output_index_image = Image::create (Path::join (output_fixel_folder, Path::basename (input_index_image.name())), input_index_image); - threaded_copy (input_index_image, output_index_image); - for (auto& header : FixelFormat::find_data_headers (input_fixel_folder, input_index_image)) { - auto input = header.get_image(); - auto output = Image::create (Path::join (output_fixel_folder, Path::basename (input.name())), input); - threaded_copy (input, output); - } + FixelFormat::copy_index_file (input_fixel_folder, output_fixel_folder, false); + FixelFormat::copy_all_data_files (input_fixel_folder, output_fixel_folder, false); } } From 7a1d1a3c9a71e2d6e88ba1cc546a5ac7274beaed Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Fri, 19 Aug 2016 14:29:54 +1000 Subject: [PATCH 083/723] A few changes to fixelcrop. Fixed indexing issue caused by threading and stochastic offsets generated by fod2fixel --- cmd/fixelcrop.cpp | 114 ++++++++++++++++--------------------- lib/fixel_format/helpers.h | 4 +- 2 files changed, 52 insertions(+), 66 deletions(-) diff --git a/cmd/fixelcrop.cpp b/cmd/fixelcrop.cpp index eca017c21a..30b5eda411 100644 --- a/cmd/fixelcrop.cpp +++ b/cmd/fixelcrop.cpp @@ -32,97 +32,83 @@ void usage () AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) & Rami Tabarra (rami.tabarra@florey.edu.au)"; DESCRIPTION - + "Crop a fixel index image along with corresponding fixel data images"; + + "Crop a fixel index image (i.e. remove fixels) using a fixel mask"; ARGUMENTS - + Argument ("dir_in", "the input fixel folder").type_text () - + Argument ("dir_out", "the output fixel folder").type_text (); - - OPTIONS - + Option ("mask", "the threshold mask").required () - + Argument ("image").type_image_in () - + Option ("data", "specify a fixel data image filename (relative to the input fixel folder) to " - "be cropped.").allow_multiple () - + Argument ("image").type_image_in (); + + Argument ("input_fixel_folder", "the input fixel folder file to be cropped").type_text () + + Argument ("input_fixel_data_mask", "the input fixel data file to be cropped").type_image_in () + + Argument ("output_fixel_folder", "the output fixel folder").type_text (); } void run () { - const auto fixel_folder = argument[0]; - FixelFormat::check_fixel_folder (fixel_folder); + const auto in_folder = argument[0]; + FixelFormat::check_fixel_folder (in_folder); + Header in_index_header = FixelFormat::find_index_header (in_folder); + auto in_index_image = in_index_header.get_image (); - const auto out_fixel_folder = argument[1]; - auto index_image = FixelFormat::find_index_header (fixel_folder).get_image (); + auto mask_image = Image::open (argument[1]); + FixelFormat::check_fixel_size (in_index_image, mask_image); - auto opt = get_options ("mask"); - auto mask_image = Image::open (std::string (opt[0][0])); - - FixelFormat::check_fixel_size (index_image, mask_image); + const auto out_fixel_folder = argument[2]; FixelFormat::check_fixel_folder (out_fixel_folder, true); - Header out_header = Header (index_image); + Header out_header = Header (in_index_image); size_t total_nfixels = std::stoul (out_header.keyval ()[FixelFormat::n_fixels_key]); - // We need to do a first pass of the mask image to determine the cropped num. of fixels + // 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) { - if (!mask_image.value ()) + if (!mask_image.value()) total_nfixels --; } out_header.keyval ()[FixelFormat::n_fixels_key] = str (total_nfixels); - const auto index_image_basename = Path::basename (index_image.name ()); - auto out_index_image = Image::create (Path::join (out_fixel_folder, index_image_basename), out_header); - - // Crop index image - mask_image.index (1) = 0; - for (auto l = Loop ("cropping index image: " + index_image_basename, 0, 3) (index_image, out_index_image); l; ++l) { - index_image.index (3) = 0; - size_t nfixels = index_image.value (); + auto out_index_image = Image::create (Path::join (out_fixel_folder, Path::basename (in_index_image.name())), out_header); - index_image.index (3) = 1; - size_t offset = index_image.value (); - for (size_t i = 0, N = nfixels; i < N; ++i) { - mask_image.index (0) = offset + i; - if (!mask_image.value ()) - nfixels--; - } + // Open all data images and create output date images with size equal to expected number of fixels + std::vector
in_headers = FixelFormat::find_data_headers (in_folder, in_index_header, true); + std::vector > in_data_images; + std::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}); - out_index_image.index (3) = 0; - out_index_image.value () = nfixels; - - out_index_image.index (3) = 1; - out_index_image.value () = offset; + 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_folder, Path::basename (in_data_header.name())), + out_data_header).with_direct_io()); } - // Crop data images - opt = get_options ("data"); - - for (size_t n = 0, N = opt.size (); n < N; n++) { - const auto data_file = opt[n][0]; - const auto data_file_path = Path::join (fixel_folder, data_file); - - Header header = Header::open (data_file_path); - auto in_data = header.get_image (); - - check_dimensions (in_data, mask_image, {0, 2}); - - header.size (0) = total_nfixels; - auto out_data = Image::create (Path::join (out_fixel_folder, data_file), header); - - size_t offset(0); - - for (auto l = Loop ("cropping data: " + data_file, 0) (mask_image, in_data); l; ++l) { - if (mask_image.value ()) { - out_data.index (1) = offset; - out_data.row (1) = in_data.row (1); - offset++; + mask_image.index (1) = 0; + uint32_t out_offset = 0; + for (auto l = Loop ("cropping fixel image", 0, 3) (in_index_image, out_index_image); l; ++l) { + + in_index_image.index(3) = 0; + size_t in_nfixels = in_index_image.value(); + in_index_image.index(3) = 1; + uint32_t in_offset = in_index_image.value(); + + size_t out_nfixels = 0; + for (size_t i = 0; i < in_nfixels; ++i) { + mask_image.index(0) = in_offset + i; + if (mask_image.value()) { + for (size_t d = 0; d < in_data_images.size(); ++d) { + out_data_images[d].index(0) = out_offset + out_nfixels; + in_data_images[d].index(0) = in_offset + i; + out_data_images[d].row(1) = in_data_images[d].row(1); + } + out_nfixels++; } } + out_index_image.index(3) = 0; + out_index_image.value() = out_nfixels; + out_index_image.index(3) = 1; + out_index_image.value() = (out_nfixels) ? out_offset : 0; + out_offset += out_nfixels; } } - diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index ca7d2bea15..82ab438024 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -126,7 +126,7 @@ namespace MR } - inline std::vector
find_data_headers (const std::string &fixel_folder_path, const Header &index_header) + inline std::vector
find_data_headers (const std::string &fixel_folder_path, const Header &index_header, const bool include_directions = false) { check_index_image (index_header); @@ -139,7 +139,7 @@ namespace MR Header H; if (Path::has_suffix (fname, FixelFormat::supported_fixel_formats) && is_data_image (H = Header::open (full_path))) { if (fixels_match (index_header, H)) { - if (!is_directions_image (H)) + if (!is_directions_image (H) || include_directions) data_headers.emplace_back (std::move (H)); } else { WARN ("fixel data file (" + fname + ") does not contain the same number of elements as fixels in the index file" ); From 7a552988066d338082e10c4bb09f7db03fc1e8af Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Fri, 19 Aug 2016 15:46:37 +1000 Subject: [PATCH 084/723] Ported fixel2sh to new fixel format --- cmd/fixel2sh.cpp | 87 ++++++++++++++++++++++--------------- cmd/fixelcorrespondence.cpp | 13 ++++-- lib/fixel_format/helpers.h | 22 +++++----- 3 files changed, 73 insertions(+), 49 deletions(-) diff --git a/cmd/fixel2sh.cpp b/cmd/fixel2sh.cpp index 5fa5d97fb5..d6db6fd4fa 100644 --- a/cmd/fixel2sh.cpp +++ b/cmd/fixel2sh.cpp @@ -22,68 +22,85 @@ #include "math/SH.h" -#include "sparse/fixel_metric.h" -#include "sparse/image.h" - +#include "fixel_format/helpers.h" +#include "fixel_format/keys.h" using namespace MR; using namespace App; -using Sparse::FixelMetric; - - - void usage () { - AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; + AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au) & David Raffelt (david.raffelt@florey.edu.au)"; DESCRIPTION - + "convert a fixel-based sparse-data image into an SH image that can be visually evaluated using MRview"; + + "convert a fixel-based sparse-data image into an spherical harmonic image " + "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."; ARGUMENTS - + Argument ("fixel_in", "the input sparse fixel image.").type_image_in () + + 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); } - - void run () { - Header H_in = Header::open (argument[0]); - Sparse::Image fixel (H_in); - - const size_t lmax = 8; - const size_t N = Math::SH::NforL (lmax); + 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 folder)"); + + Header in_data_header = Header::open (input_file); + FixelFormat::check_data_file (in_data_header); + auto in_data_image = in_data_header.get_image(); + + Header in_index_header = FixelFormat::find_index_header (Path::dirname (argument[0])); + if (input_file == in_index_header.name()); + throw Exception ("input fixel data file cannot be the directions file"); + auto in_index_image = in_index_header.get_image(); + auto in_directions_image = FixelFormat::find_directions_header (Path::dirname (argument[0]), in_index_header).get_image().with_direct_io(); + + size_t lmax = 8; + 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); - Header H_out (H_in); - H_out.datatype() = DataType::Float32; - H_out.datatype().set_byte_order_native(); - const size_t sh_dim = H_in.ndim(); - H_out.ndim() = H_in.ndim() + 1; - H_out.size (sh_dim) = N; + 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; - auto sh = Image::create (argument[1], H_out); - std::vector values; + auto sh_image = Image::create (argument[1], out_header); + std::vector sh_values; Eigen::Matrix apsf_values; - for (auto l1 = Loop("converting sparse fixel data to SH image", fixel) (fixel, sh); l1; ++l1) { - values.assign (N, 0.0); - for (size_t index = 0; index != fixel.value().size(); ++index) { - apsf_values = aPSF (apsf_values, fixel.value()[index].dir); - const default_type scale_factor = fixel.value()[index].value; - for (size_t i = 0; i != N; ++i) - values[i] += apsf_values[i] * scale_factor; + for (auto l1 = Loop ("converting fixel image to sherical harmonic image", in_index_image) (in_index_image, sh_image); l1; ++l1) { + sh_values.assign (n_sh_coeff, 0.0); + in_index_image.index(3) = 0; + uint32_t num_fixels_in_voxel = in_index_image.value(); + in_index_image.index(3) = 1; + uint32_t offset = in_index_image.value(); + + for (size_t fixel = 0; fixel < num_fixels_in_voxel; ++fixel) { + in_directions_image.index(0) = offset + fixel; + apsf_values = aPSF (apsf_values, in_directions_image.row(1)); + in_data_image.index(0) = offset + fixel; + 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(sh_dim) (sh); l2; ++l2) - sh.value() = values[sh.index(sh_dim)]; + for (auto l2 = Loop (3) (sh_image); l2; ++l2) + sh_image.value() = sh_values[sh_image.index(3)]; } - } diff --git a/cmd/fixelcorrespondence.cpp b/cmd/fixelcorrespondence.cpp index 2a63555bed..8fc2f6def8 100644 --- a/cmd/fixelcorrespondence.cpp +++ b/cmd/fixelcorrespondence.cpp @@ -52,9 +52,16 @@ 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)); - auto subject_index = FixelFormat::find_index_header (Path::dirname (argument[0])).get_image(); - auto subject_directions = FixelFormat::find_directions_header (Path::dirname (argument[0]), subject_index).get_image().with_direct_io(); - auto subject_data = Image::open (argument[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 folder)"); + + auto subject_index = FixelFormat::find_index_header (Path::dirname (input_file)).get_image(); + auto subject_directions = FixelFormat::find_directions_header (Path::dirname (input_file), subject_index).get_image().with_direct_io(); + + if (input_file == subject_directions.name()); + throw Exception ("input fixel data file cannot be the directions file"); + auto subject_data = Image::open (input_file); FixelFormat::check_fixel_size (subject_index, subject_data); auto template_index = FixelFormat::find_index_header (argument[1]).get_image(); diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index 82ab438024..b9bf8d8644 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -44,23 +44,23 @@ namespace MR } - inline bool is_data_image (const Header& in) + inline bool is_data_file (const Header& in) { return in.ndim () == 3 && in.size (2) == 1; } - inline bool is_directions_image (const Header& in) + inline bool is_directions_file (const Header& in) { std::string basename (Path::basename (in.name())); return in.ndim () == 3 && in.size (1) == 3 && in.size (2) == 1 && (basename.substr(0, basename.find_last_of(".")) == "directions"); } - inline void check_data_image (const Header& in) + inline void check_data_file (const Header& in) { - if (!is_data_image (in)) - throw InvalidImageException (in.name () + " is not a valid fixel data image. Expected a 3-dimensionl image of size n x m x 1"); + if (!is_data_file (in)) + throw InvalidImageException (in.name () + " is not a valid fixel data file. Expected a 3-dimensional image of size n x m x 1"); } @@ -80,7 +80,7 @@ namespace MR inline void check_fixel_size (const Header& index_h, const Header& data_h) { check_index_image (index_h); - check_data_image (data_h); + check_data_file (data_h); if (!fixels_match (index_h, data_h)) throw InvalidImageException ("Fixel number mismatch between index image " + index_h.name() + " and data image " + data_h.name()); @@ -137,9 +137,9 @@ namespace MR while ((fname = dir_walker.read_name ()).size ()) { auto full_path = Path::join (fixel_folder_path, fname); Header H; - if (Path::has_suffix (fname, FixelFormat::supported_fixel_formats) && is_data_image (H = Header::open (full_path))) { + if (Path::has_suffix (fname, FixelFormat::supported_fixel_formats) && is_data_file (H = Header::open (full_path))) { if (fixels_match (index_header, H)) { - if (!is_directions_image (H) || include_directions) + if (!is_directions_file (H) || include_directions) data_headers.emplace_back (std::move (H)); } else { WARN ("fixel data file (" + fname + ") does not contain the same number of elements as fixels in the index file" ); @@ -164,8 +164,8 @@ namespace MR Header tmp_header; auto full_path = Path::join (fixel_folder_path, fname); if (Path::has_suffix (fname, FixelFormat::supported_fixel_formats) - && is_directions_image (tmp_header = Header::open (full_path))) { - if (is_directions_image (tmp_header)) { + && is_directions_file (tmp_header = Header::open (full_path))) { + if (is_directions_file (tmp_header)) { if (fixels_match (index_header, tmp_header)) { if (directions_found == true) throw Exception ("multiple directions files found in fixel image folder: " + fixel_folder_path); @@ -199,7 +199,7 @@ namespace MR if (input_header.keyval().at (n_fixels_key) != output_header.keyval().at (n_fixels_key)) throw InvalidFixelDirectoryException ("Output fixel folder (" + output_folder + ") already contains fixel " "index file with a different number of fixels to the generated output"); - } else if (is_data_image (input_header)) { + } else if (is_data_file (input_header)) { if (input_header.size(2) != output_header.size(2)) throw InvalidFixelDirectoryException ("Output fixel folder (" + output_folder + ") already contains fixel " "file " + input_header.name() + ") with a different number of fixels to the generated output"); From be16a1e386d71fe6d062a60943b7e1a8889bc349 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Fri, 19 Aug 2016 17:07:29 +1000 Subject: [PATCH 085/723] Few fixes to fixelcorrespondence --- cmd/fixelcorrespondence.cpp | 36 +++++++++++++--------- lib/fixel_format/helpers.h | 3 +- src/gui/mrview/tool/vector/fixelfolder.cpp | 1 - 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/cmd/fixelcorrespondence.cpp b/cmd/fixelcorrespondence.cpp index 8fc2f6def8..33e2ff875e 100644 --- a/cmd/fixelcorrespondence.cpp +++ b/cmd/fixelcorrespondence.cpp @@ -24,7 +24,7 @@ using namespace MR; using namespace App; -#define DEFAULT_ANGLE_THRESHOLD 30.0 +#define DEFAULT_ANGLE_THRESHOLD 45.0 void usage () { @@ -36,10 +36,10 @@ void usage () "The output fixel image will have the same fixels (and directions) of the template."; ARGUMENTS - + Argument ("subject_fixel_data", "the input subject fixel data file. This should be a file inside the fixel folder").type_image_in () - + Argument ("template_fixel_folder", "the input template fixel folder.").type_image_in () - + Argument ("output_fixel_folder", "the output fixel folder. Any existing index file in the output folder will be checked that it matches the expected output.").type_text() - + Argument ("output_fixel_file", "the output fixel data file. This will be placed in the output fixel folder").type_image_out (); + + Argument ("subject_data", "the input subject fixel data file. This should be a file inside the fixel folder").type_image_in () + + Argument ("template_folder", "the input template fixel folder.").type_image_in () + + Argument ("output_folder", "the output fixel folder. Any existing index file in the output folder will be checked that it matches the expected output.").type_text() + + Argument ("output_data", "the name of the output fixel data file. This will be placed in the output fixel folder").type_image_out (); OPTIONS + Option ("angle", "the max angle threshold for computing inter-subject fixel correspondence (Default: " + str(DEFAULT_ANGLE_THRESHOLD, 2) + " degrees)") @@ -59,8 +59,9 @@ void run () auto subject_index = FixelFormat::find_index_header (Path::dirname (input_file)).get_image(); auto subject_directions = FixelFormat::find_directions_header (Path::dirname (input_file), subject_index).get_image().with_direct_io(); - if (input_file == subject_directions.name()); + if (input_file == subject_directions.name()) throw Exception ("input fixel data file cannot be the directions file"); + auto subject_data = Image::open (input_file); FixelFormat::check_fixel_size (subject_index, subject_data); @@ -69,27 +70,32 @@ void run () check_dimensions (subject_index, template_index); std::string output_fixel_folder = argument[2]; - FixelFormat::copy_index_file (Path::dirname (argument[1]), output_fixel_folder, true); - FixelFormat::copy_directions_file (Path::dirname (argument[1]), output_fixel_folder, true); + FixelFormat::copy_index_file (argument[1], output_fixel_folder, true); + FixelFormat::copy_directions_file (argument[1], output_fixel_folder, true); - auto output_data = Image::create (Path::join (output_fixel_folder, argument[3]), subject_data); + Header output_data_header (template_directions); + output_data_header.size(1) = 1; + auto output_data = Image::create (Path::join (output_fixel_folder, argument[3]), output_data_header); for (auto i = Loop ("mapping subject fixels to template fixels", template_index, 0, 3)(template_index, subject_index); i; ++i) { template_index.index(3) = 0; uint32_t nfixels_template = template_index.value(); template_index.index(3) = 1; uint32_t template_offset = template_index.value(); + for (size_t t = 0; t < nfixels_template; ++t) { + float largest_dp = 0.0; int index_of_closest_fixel = -1; subject_index.index(3) = 0; - uint32_t nfixels_subect = subject_index.value(); + uint32_t nfixels_subject = subject_index.value(); subject_index.index(3) = 1; uint32_t subject_offset = subject_index.value(); - template_directions.index(1) = template_offset + t; - for (size_t s = 0; s < nfixels_subect; ++s) { - subject_directions.index(1) = subject_offset + s; + + template_directions.index(0) = template_offset + t; + for (size_t s = 0; s < nfixels_subject; ++s) { + subject_directions.index(0) = subject_offset + s; float dp = std::abs (template_directions.row(1).dot (subject_directions.row(1))); if (dp > largest_dp) { largest_dp = dp; @@ -97,8 +103,8 @@ void run () } } if (largest_dp > angular_threshold_dp) { - output_data.index(1) = template_offset + t; - subject_data.index(1) = subject_offset + index_of_closest_fixel; + output_data.index(0) = template_offset + t; + subject_data.index(0) = subject_offset + index_of_closest_fixel; output_data.value() = subject_data.value(); } } diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index b9bf8d8644..3bc3cd2136 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -69,9 +69,8 @@ namespace MR { bool fixels_match (false); - if (is_index_image (index_header)) { + if (is_index_image (index_header)) fixels_match = std::stoul(index_header.keyval ().at (n_fixels_key)) == (unsigned long)data_header.size (0); - } return fixels_match; } diff --git a/src/gui/mrview/tool/vector/fixelfolder.cpp b/src/gui/mrview/tool/vector/fixelfolder.cpp index 5bed921777..a75b0f7a78 100644 --- a/src/gui/mrview/tool/vector/fixelfolder.cpp +++ b/src/gui/mrview/tool/vector/fixelfolder.cpp @@ -61,7 +61,6 @@ namespace MR // Load fixel direction images auto directions_image = FixelFormat::find_directions_header (Path::dirname (fixel_data->name ()), *fixel_data).get_image ().with_direct_io (); - std::cout << directions_image.name() << std::endl; directions_image.index (1) = 0; for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { fixel_data->index (3) = 0; From 2ea173441df0cf6537bf87e813eefd67a93341d5 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Fri, 19 Aug 2016 17:55:24 +1000 Subject: [PATCH 086/723] Add FixelFormat convenience function for loading data files --- cmd/fixel2sh.cpp | 17 ++++------------- cmd/fixelcorrespondence.cpp | 4 ++-- lib/fixel_format/helpers.h | 30 ++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/cmd/fixel2sh.cpp b/cmd/fixel2sh.cpp index d6db6fd4fa..6f7c11172e 100644 --- a/cmd/fixel2sh.cpp +++ b/cmd/fixel2sh.cpp @@ -52,20 +52,11 @@ void usage () void run () { + auto in_data_image = FixelFormat::open_fixel_data_file (argument[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 folder)"); - - Header in_data_header = Header::open (input_file); - FixelFormat::check_data_file (in_data_header); - auto in_data_image = in_data_header.get_image(); - - Header in_index_header = FixelFormat::find_index_header (Path::dirname (argument[0])); - if (input_file == in_index_header.name()); - throw Exception ("input fixel data file cannot be the directions file"); - auto in_index_image = in_index_header.get_image(); - auto in_directions_image = FixelFormat::find_directions_header (Path::dirname (argument[0]), in_index_header).get_image().with_direct_io(); + Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_folder (argument[0])); + auto in_index_image =in_index_header.get_image(); + auto in_directions_image = FixelFormat::find_directions_header (FixelFormat::get_fixel_folder (argument[0]), in_index_header).get_image().with_direct_io(); size_t lmax = 8; auto opt = get_options ("lmax"); diff --git a/cmd/fixelcorrespondence.cpp b/cmd/fixelcorrespondence.cpp index 33e2ff875e..8f4973d0f2 100644 --- a/cmd/fixelcorrespondence.cpp +++ b/cmd/fixelcorrespondence.cpp @@ -56,8 +56,8 @@ void run () if (Path::is_dir (input_file)) throw Exception ("please input the specific fixel data file to be converted (not the fixel folder)"); - auto subject_index = FixelFormat::find_index_header (Path::dirname (input_file)).get_image(); - auto subject_directions = FixelFormat::find_directions_header (Path::dirname (input_file), subject_index).get_image().with_direct_io(); + auto subject_index = FixelFormat::find_index_header (FixelFormat::get_fixel_folder (input_file)).get_image(); + auto subject_directions = FixelFormat::find_directions_header (FixelFormat::get_fixel_folder (input_file), subject_index).get_image().with_direct_io(); if (input_file == subject_directions.name()) throw Exception ("input fixel data file cannot be the directions file"); diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index 3bc3cd2136..cf2a36b9d1 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -63,6 +63,13 @@ namespace MR throw InvalidImageException (in.name () + " is not a valid fixel data file. Expected a 3-dimensional image of size n x m x 1"); } + inline std::string get_fixel_folder (const std::string& fixel_file) { + std::string fixel_folder = Path::dirname (fixel_file); + // assume the user is running the command from within the fixel directory + if (fixel_folder.empty()) + fixel_folder = Path::cwd(); + return fixel_folder; + } template inline bool fixels_match (const IndexHeaderType& index_header, const DataHeaderType& data_header) @@ -88,6 +95,12 @@ namespace MR inline void check_fixel_folder (const std::string &path, bool create_if_missing = false, bool check_if_empty = false) { + + // the user must be inside the fixel folder + if (path.empty()) { + + } + bool exists (true); if (!(exists = Path::exists (path))) { @@ -235,6 +248,23 @@ namespace MR copy_fixel_file (input_header.name(), output_folder, check_existing_output); } + //! open a data file. checks that a user has not input a fixel folder or index image + template + Image open_fixel_data_file (const std::string& input_file) { + if (Path::is_dir (input_file)) + throw Exception ("please input the specific fixel data file to be converted (not the fixel folder)"); + + Header in_data_header = Header::open (input_file); + FixelFormat::check_data_file (in_data_header); + auto in_data_image = in_data_header.get_image(); + + Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_folder (input_file)); + if (input_file == in_index_header.name()) + throw Exception ("input fixel data file cannot be the index file"); + + return in_data_image; + } + } } From f9d690b3a3b8b3e4d24587b1f3c6435dd8815bce Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sat, 20 Aug 2016 14:52:55 +1000 Subject: [PATCH 087/723] Second attempt at system signal handling Test for ability to set signal handlers at configure step, and generate static lookup table of error messages during configure step. Delete piped images if program is terminated abruptly. --- lib/signals/signals.cpp | 83 +++++++++++++++++++++++++++++++++++++++++ lib/signals/signals.h | 44 ++++++++++++++++++++++ lib/signals/table.h | 29 ++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 lib/signals/signals.cpp create mode 100644 lib/signals/signals.h create mode 100644 lib/signals/table.h diff --git a/lib/signals/signals.cpp b/lib/signals/signals.cpp new file mode 100644 index 0000000000..e533fc7374 --- /dev/null +++ b/lib/signals/signals.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "signals/signals.h" + +#include +#include +#include +#include + +#include "exception.h" +#include "signals/table.h" + +#ifndef MRTRIX_NO_SIGNAL_HANDLING + +namespace MR +{ + namespace Signals + { + + + + void init() + { + // Construct the signal structure + struct sigaction act; + act.sa_handler = &_handler; + sigemptyset (&act.sa_mask); +#ifdef SIGINT + sigaddset (&act.sa_mask, SIGINT); +#endif +#ifdef SIGQUIT + sigaddset (&act.sa_mask, SIGQUIT); +#endif + act.sa_flags = 0; + + // Set the handler for a priori known signals only + for (size_t i = 0; i != NSIG; ++i) { + if (table[i][0]) + sigaction (i, &act, nullptr); + } + + // Set function to delete temporary files if the signal handler is invoked + std::at_quick_exit (_at_quick_exit); + } + + + + void _handler (int i) + { + // Don't attempt to use any terminal colouring + std::cerr << App::NAME << ": [SYSTEM FATAL CODE: " << i << "] " << table[i] << "\n"; + std::quick_exit (i); + } + + + + void _at_quick_exit() + { + if (pipe_in.size()) + unlink (pipe_in.c_str()); + if (pipe_out.size()) + unlink (pipe_out.c_str()); + } + + + + } +} + +#endif diff --git a/lib/signals/signals.h b/lib/signals/signals.h new file mode 100644 index 0000000000..8d203cf2b1 --- /dev/null +++ b/lib/signals/signals.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __signals_h__ +#define __signals_h__ + +#ifndef MRTRIX_NO_SIGNAL_HANDLING + +#include + +namespace MR +{ + namespace Signals + { + + + std::string pipe_in, pipe_out; + + + void init(); + + void _handler (int) noexcept; + void _at_quick_exit() noexcept; + + + + } +} + +#endif + +#endif diff --git a/lib/signals/table.h b/lib/signals/table.h new file mode 100644 index 0000000000..3933277de6 --- /dev/null +++ b/lib/signals/table.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __signals_table_h__ +#define __signals_table_h__ + +namespace MR +{ + namespace Signals + { + + extern const char* table[]; + + } +} + +#endif From ae3a6272bdcf13860fbba2bfea3fe4da6388b287 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sat, 20 Aug 2016 17:48:43 +1000 Subject: [PATCH 088/723] Files missing from previous commit --- .gitignore | 1 + configure | 135 ++++++++++++++++++++++++++++++++++++++++++- lib/app.cpp | 8 +++ lib/formats/pipe.cpp | 9 +++ 4 files changed, 152 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 70188b9584..8682261ae7 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ icons.cpp /build.log /test/build.log /lib/version.cpp +/lib/signals/table.cpp /src/project_version.h /test/src/project_version.h /scripts/mrtrix_bash_completion diff --git a/configure b/configure index c5a02adbc6..2e10bfe0b9 100755 --- a/configure +++ b/configure @@ -995,7 +995,7 @@ int main() { Foo f; } cmd = [ os.path.join(qt_dir.name, 'qt') ] ret = execute (cmd, RuntimeError) - report (ret[1]) + report (ret[1] + '\n') except QMakeError: @@ -1079,6 +1079,139 @@ if R_module: + +report ('Checking ability to handle system signals: ') +try: + handle_signals = compile(''' +#include +void handler (int) { abort(); } +int main () +{ + struct sigaction act; + act.sa_handler = &handler; + sigemptyset (&act.sa_mask); + act.sa_flags = 0; + sigaction (1, &act, nullptr); + const int nsig = NSIG; + return 0; +} +''', cpp_flags, ld_flags) + report ('Yes\n') + + # Compile and run code to generate the signal lookup table + report ('Generating system signal lookup table: ') + make_table_binary_path = compile (''' +#include +#include +#include +#include + +int main () +{ + std::string messages[NSIG]; + messages[0] = "Null system signal received"; +#ifdef SIGALRM + messages[SIGALRM] = "[SIGALRM] Timer expiration"; +#endif +#ifdef SIGBUS + messages[SIGBUS] = "[SIGBUS] Bus error: Accessing invalid address (out of storage space?)"; +#endif +#ifdef SIGFPE + messages[SIGFPE] = "[SIGFPE] Floating-point arithmetic exception"; +#endif +#ifdef SIGHUP + messages[SIGHUP] = "[SIGHUP] Disconnection of terminal"; +#endif +#ifdef SIGILL + messages[SIGILL] = "[SIGILL] Illegal instruction (corrupt binary command file?)"; +#endif +#ifdef SIGINT + messages[SIGINT] = "[SIGINT] Program manually interrupted by terminal"; +#endif +#ifdef SIGPIPE + messages[SIGPIPE] = "[SIGPIPE] Nothing on receiving end of pipe"; +#endif +#ifdef SIGPWR + messages[SIGPWR] = "[SIGPWR] Power failure restart"; +#endif +#ifdef SIGQUIT + messages[SIGQUIT] = "[SIGQUIT] Received terminal quit signal"; +#endif +#ifdef SIGSEGV + messages[SIGSEGV] = "[SIGSEGV] Segmentation fault: Invalid memory reference"; +#endif +#ifdef SIGSYS + messages[SIGSYS] = "[SIGSYS] Bad system call"; +#endif +#ifdef SIGTERM + messages[SIGTERM] = "[SIGTERM] Terminated by kill command"; +#endif +#ifdef SIGXCPU + messages[SIGXCPU] = "[SIGXCPU] CPU time limit exceeded"; +#endif +#ifdef SIGXFSZ + messages[SIGXFSZ] = "[SIGXFSZ] File size limit exceeded"; +#endif + + std::ofstream out ("lib/signals/table.cpp", std::ios_base::trunc); + if (!out) { + std::cerr << "Unable to open file lib/signals/table.cpp for writing\\n"; + return 1; + } + + out << +"/*\\n" +" * Copyright (c) 2008-2016 the MRtrix3 contributors\\n" +" * \\n" +" * This Source Code Form is subject to the terms of the Mozilla Public\\n" +" * License, v. 2.0. If a copy of the MPL was not distributed with this\\n" +" * file, You can obtain one at http://mozilla.org/MPL/2.0/\\n" +" * \\n" +" * MRtrix is distributed in the hope that it will be useful,\\n" +" * but WITHOUT ANY WARRANTY; without even the implied warranty of\\n" +" * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\\n" +" * \\n" +" * For more details, see www.mrtrix.org\\n" +" * \\n" +" */\\n" +"\\n" +"// File auto-generated by configure script\\n" +"\\n" +"namespace MR\\n" +"{\\n" +" namespace Signals\\n" +" {\\n" +"\\n" +" const char* table[] = {\\n"; + for (int i = 0; i != NSIG; ++i) { + out << " \\"" << messages[i] << "\\""; + if (i == NSIG-1) + out << " };\\n"; + else + out << ",\\n"; + } + out << +"\\n" +" }\\n" +"}\\n" +"\\n"; + + return 0; +} +''', cpp_flags, ld_flags) + report ('Done\n') + +except: + report ('No\n') + if os.path.isfile('lib/signals/table.cpp'): + os.unlink('lib/signals/table.cpp') + cpp_flags += [ '-DMRTRIX_NO_SIGNAL_HANDLING' ] + + + + + + # add debugging or profiling flags if requested: cpp_flags += [ '-Wall' ] diff --git a/lib/app.cpp b/lib/app.cpp index 80391e6840..30584f0bd5 100644 --- a/lib/app.cpp +++ b/lib/app.cpp @@ -22,6 +22,10 @@ #include "file/path.h" #include "file/config.h" +#ifndef MRTRIX_NO_SIGNAL_HANDLING +#include "signals/signals.h" +#endif + #define MRTRIX_HELP_COMMAND "less -X" #define HELP_WIDTH 80 @@ -1029,6 +1033,10 @@ namespace MR setvbuf (stdout, nullptr, _IOLBF, 0); #endif +#ifndef MRTRIX_NO_SIGNAL_HANDLING + Signals::init(); +#endif + terminal_use_colour = !ProgressBar::set_update_method(); argc = cmdline_argc; diff --git a/lib/formats/pipe.cpp b/lib/formats/pipe.cpp index 61fc3080c1..4415347661 100644 --- a/lib/formats/pipe.cpp +++ b/lib/formats/pipe.cpp @@ -18,6 +18,7 @@ #include "header.h" #include "image_io/pipe.h" #include "formats/list.h" +#include "signals/signals.h" namespace MR { @@ -39,6 +40,10 @@ namespace MR if (H.name().empty()) throw Exception ("no filename supplied to standard input (broken pipe?)"); +#ifndef MRTRIX_NO_SIGNAL_HANDLING + Signals::pipe_in = H.name(); +#endif + if (!Path::has_suffix (H.name(), ".mif")) throw Exception ("MRtrix only supports the .mif format for command-line piping"); @@ -58,6 +63,10 @@ namespace MR H.name() = File::create_tempfile (0, "mif"); +#ifndef MRTRIX_NO_SIGNAL_HANDLING + Signals::pipe_out = H.name(); +#endif + return mrtrix_handler.check (H, num_axes); } From 66725e50a2e9e2ae7913f2a86c48ee59633746c1 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sat, 20 Aug 2016 20:57:10 +1000 Subject: [PATCH 089/723] dwiextract: Extract volumes according to phase encoding --- cmd/dwiextract.cpp | 38 ++++++++++++++++++++++++-- docs/reference/commands/dwiextract.rst | 7 ++++- lib/phase_encoding.cpp | 5 ++++ lib/phase_encoding.h | 1 + 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/cmd/dwiextract.cpp b/cmd/dwiextract.cpp index fbc52adeff..8b5b20eb2e 100644 --- a/cmd/dwiextract.cpp +++ b/cmd/dwiextract.cpp @@ -39,13 +39,14 @@ void usage () ARGUMENTS + Argument ("input", "the input DW image.").type_image_in () - + Argument ("output", "the output image (diffusion-weighted volumes by default.").type_image_out (); + + 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.") + DWI::GradImportOptions() + DWI::ShellOption + PhaseEncoding::ImportOptions + + PhaseEncoding::SelectOptions + Stride::Options; } @@ -70,7 +71,10 @@ void run() volumes.push_back (v); } bzero = (shells.count() == 1 && shells[0].is_bzero()); - } else { + // 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 ("BValueThreshold", 10.0); for (ssize_t row = 0; row != grad.rows(); ++row) { if ((bzero && (grad (row, 3) < bzero_threshold)) || (!bzero && (grad (row, 3) > bzero_threshold))) @@ -78,6 +82,35 @@ void run() } } + opt = get_options ("pe"); + const auto pe_scheme = PhaseEncoding::get_scheme (input_header); + 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]); + if (!(filter.size() == 3 || filter.size() == 4)) + throw Exception ("Phase encoding filter must be a comma-separated list of either 3 or 4 numbers"); + std::vector new_volumes; + for (const auto i : volumes) { + bool keep = true; + for (size_t axis = 0; axis != 3; ++axis) { + if (pe_scheme(i, axis) != filter[axis]) { + keep = false; + break; + } + } + if (filter.size() == 4) { + if (std::abs (pe_scheme(i, 3) - filter[3]) > 5e-4) { + keep = false; + break; + } + } + if (keep) + new_volumes.push_back (i); + } + std::swap (volumes, new_volumes); + } + if (volumes.empty()) { auto type = (bzero) ? "b=0" : "dwi"; throw Exception ("No " + str(type) + " volumes present"); @@ -94,7 +127,6 @@ void run() new_grad.row (i) = grad.row (volumes[i]); DWI::set_DW_scheme (header, new_grad); - const auto pe_scheme = PhaseEncoding::get_scheme (input_header); if (pe_scheme.rows()) { Eigen::MatrixXd new_scheme (volumes.size(), pe_scheme.cols()); for (size_t i = 0; i != volumes.size(); ++i) diff --git a/docs/reference/commands/dwiextract.rst b/docs/reference/commands/dwiextract.rst index c9dcdded0c..6691365723 100644 --- a/docs/reference/commands/dwiextract.rst +++ b/docs/reference/commands/dwiextract.rst @@ -11,7 +11,7 @@ Synopsis dwiextract [ options ] input output - *input*: the input DW image. -- *output*: the output image (diffusion-weighted volumes by default. +- *output*: the output image (diffusion-weighted volumes by default). Description ----------- @@ -44,6 +44,11 @@ Options for importing phase-encode tables - **-import_pe_eddy config indices** import phase-encoding information from an EDDY-style config / index file pair +Options for selecting volumes based on phase-encoding +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-pe desc** select volumes with a particular phase encoding; this can be three comma-separated values (for i,j,k components of vector direction) or four (direction & total readout time) + Stride options ^^^^^^^^^^^^^^ diff --git a/lib/phase_encoding.cpp b/lib/phase_encoding.cpp index bc713755de..39fef145ca 100644 --- a/lib/phase_encoding.cpp +++ b/lib/phase_encoding.cpp @@ -30,6 +30,11 @@ namespace MR + Argument ("config").type_file_in() + Argument ("indices").type_file_in(); + const OptionGroup SelectOptions = OptionGroup ("Options for selecting volumes based on phase-encoding") + + Option ("pe", "select volumes with a particular phase encoding; " + "this can be three comma-separated values (for i,j,k components of vector direction) or four (direction & total readout time)") + + Argument ("desc").type_sequence_float(); + const OptionGroup ExportOptions = OptionGroup ("Options for exporting phase-encode tables") + Option ("export_pe_table", "export phase-encoding table to file") + Argument ("file").type_file_out() diff --git a/lib/phase_encoding.h b/lib/phase_encoding.h index a1e0633922..6f6d42e098 100644 --- a/lib/phase_encoding.h +++ b/lib/phase_encoding.h @@ -32,6 +32,7 @@ namespace MR extern const App::OptionGroup ImportOptions; + extern const App::OptionGroup SelectOptions; extern const App::OptionGroup ExportOptions; From 1560f171e9337c5cfaaafa26be27d721f7d39bc5 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Mon, 22 Aug 2016 14:12:55 +1000 Subject: [PATCH 090/723] Ported fixel2tsf to new fixel format --- cmd/fixel2tsf.cpp | 55 +++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/cmd/fixel2tsf.cpp b/cmd/fixel2tsf.cpp index db34b722ba..1b7793fe8f 100644 --- a/cmd/fixel2tsf.cpp +++ b/cmd/fixel2tsf.cpp @@ -19,9 +19,8 @@ #include "algo/loop.h" #include "image.h" -#include "sparse/fixel_metric.h" -#include "sparse/keys.h" -#include "sparse/image.h" +#include "fixel_format/helpers.h" +#include "fixel_format/keys.h" #include "dwi/tractography/file.h" #include "dwi/tractography/scalar_file.h" @@ -34,11 +33,7 @@ using namespace MR; using namespace App; - -using Sparse::FixelMetric; - - -#define DEFAULT_ANGULAR_THRESHOLD 30.0 +#define DEFAULT_ANGULAR_THRESHOLD 45.0 @@ -49,10 +44,10 @@ void usage () DESCRIPTION + "Map fixel values to a track scalar file based on an input tractogram. " - "This is useful for visualising the output from fixelcfestats in 3D."; + "This is useful for visualising all brain fixels (e.g. the output from fixelcfestats) in 3D."; ARGUMENTS - + Argument ("fixel_in", "the input fixel image").type_image_in () + + Argument ("fixel_in", "the input fixel data file").type_image_in () + Argument ("tracks", "the input track file ").type_tracks_in () + Argument ("tsf", "the output track scalar file").type_file_out (); @@ -70,11 +65,16 @@ typedef DWI::Tractography::Mapping::SetVoxelDir SetVoxelDir; void run () { - DWI::Tractography::Properties properties; + auto in_data_image = FixelFormat::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"); - auto input_header = Header::open (argument[0]); - Sparse::Image input_fixel (argument[0]); + Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_folder (argument[0])); + auto in_index_image = in_index_header.get_image(); + auto in_directions_image = FixelFormat::find_directions_header (FixelFormat::get_fixel_folder (argument[0]), in_index_header).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])); @@ -83,17 +83,17 @@ void run () 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)); + const float angular_threshold_dp = cos (angular_threshold * (Math::pi / 180.0)); const size_t num_tracks = properties["count"].empty() ? 0 : to (properties["count"]); - DWI::Tractography::Mapping::TrackMapperBase mapper (input_header); + DWI::Tractography::Mapping::TrackMapperBase mapper (in_index_image); mapper.set_use_precise_mapping (true); ProgressBar progress ("mapping fixel values to streamline points", num_tracks); DWI::Tractography::Streamline tck; - Transform transform (input_fixel); + Transform transform (in_index_image); Eigen::Vector3 voxel_pos; while (reader (tck)) { @@ -104,22 +104,31 @@ void run () 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 (input_fixel); + assign_pos_of (*d).to (in_index_image); Eigen::Vector3f dir = d->get_dir().cast(); dir.normalize(); float largest_dp = 0.0; int32_t closest_fixel_index = -1; - for (size_t f = 0; f != input_fixel.value().size(); ++f) { - float dp = std::abs (dir.dot (input_fixel.value()[f].dir)); + + in_index_image.index(3) = 0; + uint32_t num_fixels_in_voxel = in_index_image.value(); + in_index_image.index(3) = 1; + uint32_t offset = in_index_image.value(); + + for (size_t fixel = 0; fixel < num_fixels_in_voxel; ++fixel) { + in_directions_image.index(0) = offset + fixel; + float dp = std::abs (dir.dot (in_directions_image.row(1))); if (dp > largest_dp) { largest_dp = dp; - closest_fixel_index = f; + closest_fixel_index = fixel; } } - if (largest_dp > angular_threshold_dp) - scalars[p] = input_fixel.value()[closest_fixel_index].value; - else + if (largest_dp > angular_threshold_dp) { + in_data_image.index(0) = offset + closest_fixel_index; + scalars[p] = in_data_image.value(); + } else { scalars[p] = 0.0; + } break; } } From ba4697109c48d0efaa33ca1196b7c0be5ae1222c Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 22 Aug 2016 14:30:37 +1000 Subject: [PATCH 091/723] Redesign of signal handling As discussed in #758. --- .gitignore | 1 - configure | 111 +---------- lib/app.cpp | 10 +- lib/app.h | 3 + lib/formats/pipe.cpp | 10 +- lib/image_io/pipe.cpp | 2 + lib/signal_handler.cpp | 209 ++++++++++++++++++++ lib/{signals/signals.h => signal_handler.h} | 32 +-- lib/signals/signals.cpp | 83 -------- lib/signals/table.h | 29 --- 10 files changed, 242 insertions(+), 248 deletions(-) create mode 100644 lib/signal_handler.cpp rename lib/{signals/signals.h => signal_handler.h} (54%) delete mode 100644 lib/signals/signals.cpp delete mode 100644 lib/signals/table.h diff --git a/.gitignore b/.gitignore index 8682261ae7..70188b9584 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,6 @@ icons.cpp /build.log /test/build.log /lib/version.cpp -/lib/signals/table.cpp /src/project_version.h /test/src/project_version.h /scripts/mrtrix_bash_completion diff --git a/configure b/configure index 2e10bfe0b9..28e5fa0d3e 100755 --- a/configure +++ b/configure @@ -1084,7 +1084,8 @@ report ('Checking ability to handle system signals: ') try: handle_signals = compile(''' #include -void handler (int) { abort(); } +#include +void handler (int i) { std::_Exit(i); } int main () { struct sigaction act; @@ -1097,115 +1098,11 @@ int main () } ''', cpp_flags, ld_flags) report ('Yes\n') - - # Compile and run code to generate the signal lookup table - report ('Generating system signal lookup table: ') - make_table_binary_path = compile (''' -#include -#include -#include -#include - -int main () -{ - std::string messages[NSIG]; - messages[0] = "Null system signal received"; -#ifdef SIGALRM - messages[SIGALRM] = "[SIGALRM] Timer expiration"; -#endif -#ifdef SIGBUS - messages[SIGBUS] = "[SIGBUS] Bus error: Accessing invalid address (out of storage space?)"; -#endif -#ifdef SIGFPE - messages[SIGFPE] = "[SIGFPE] Floating-point arithmetic exception"; -#endif -#ifdef SIGHUP - messages[SIGHUP] = "[SIGHUP] Disconnection of terminal"; -#endif -#ifdef SIGILL - messages[SIGILL] = "[SIGILL] Illegal instruction (corrupt binary command file?)"; -#endif -#ifdef SIGINT - messages[SIGINT] = "[SIGINT] Program manually interrupted by terminal"; -#endif -#ifdef SIGPIPE - messages[SIGPIPE] = "[SIGPIPE] Nothing on receiving end of pipe"; -#endif -#ifdef SIGPWR - messages[SIGPWR] = "[SIGPWR] Power failure restart"; -#endif -#ifdef SIGQUIT - messages[SIGQUIT] = "[SIGQUIT] Received terminal quit signal"; -#endif -#ifdef SIGSEGV - messages[SIGSEGV] = "[SIGSEGV] Segmentation fault: Invalid memory reference"; -#endif -#ifdef SIGSYS - messages[SIGSYS] = "[SIGSYS] Bad system call"; -#endif -#ifdef SIGTERM - messages[SIGTERM] = "[SIGTERM] Terminated by kill command"; -#endif -#ifdef SIGXCPU - messages[SIGXCPU] = "[SIGXCPU] CPU time limit exceeded"; -#endif -#ifdef SIGXFSZ - messages[SIGXFSZ] = "[SIGXFSZ] File size limit exceeded"; -#endif - - std::ofstream out ("lib/signals/table.cpp", std::ios_base::trunc); - if (!out) { - std::cerr << "Unable to open file lib/signals/table.cpp for writing\\n"; - return 1; - } - - out << -"/*\\n" -" * Copyright (c) 2008-2016 the MRtrix3 contributors\\n" -" * \\n" -" * This Source Code Form is subject to the terms of the Mozilla Public\\n" -" * License, v. 2.0. If a copy of the MPL was not distributed with this\\n" -" * file, You can obtain one at http://mozilla.org/MPL/2.0/\\n" -" * \\n" -" * MRtrix is distributed in the hope that it will be useful,\\n" -" * but WITHOUT ANY WARRANTY; without even the implied warranty of\\n" -" * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\\n" -" * \\n" -" * For more details, see www.mrtrix.org\\n" -" * \\n" -" */\\n" -"\\n" -"// File auto-generated by configure script\\n" -"\\n" -"namespace MR\\n" -"{\\n" -" namespace Signals\\n" -" {\\n" -"\\n" -" const char* table[] = {\\n"; - for (int i = 0; i != NSIG; ++i) { - out << " \\"" << messages[i] << "\\""; - if (i == NSIG-1) - out << " };\\n"; - else - out << ",\\n"; - } - out << -"\\n" -" }\\n" -"}\\n" -"\\n"; - - return 0; -} -''', cpp_flags, ld_flags) - report ('Done\n') + cpp_flags += [ '-DMRTRIX_SIGNAL_HANDLING' ] except: report ('No\n') - if os.path.isfile('lib/signals/table.cpp'): - os.unlink('lib/signals/table.cpp') - cpp_flags += [ '-DMRTRIX_NO_SIGNAL_HANDLING' ] + diff --git a/lib/app.cpp b/lib/app.cpp index 30584f0bd5..53e0fe29a2 100644 --- a/lib/app.cpp +++ b/lib/app.cpp @@ -22,10 +22,6 @@ #include "file/path.h" #include "file/config.h" -#ifndef MRTRIX_NO_SIGNAL_HANDLING -#include "signals/signals.h" -#endif - #define MRTRIX_HELP_COMMAND "less -X" #define HELP_WIDTH 80 @@ -84,6 +80,8 @@ namespace MR const char* project_version = nullptr; const char* build_date = __DATE__; + SignalHandler signal_handler; + int argc = 0; char** argv = nullptr; @@ -1033,10 +1031,6 @@ namespace MR setvbuf (stdout, nullptr, _IOLBF, 0); #endif -#ifndef MRTRIX_NO_SIGNAL_HANDLING - Signals::init(); -#endif - terminal_use_colour = !ProgressBar::set_update_method(); argc = cmdline_argc; diff --git a/lib/app.h b/lib/app.h index 973d7068a5..f5e1caa7ed 100644 --- a/lib/app.h +++ b/lib/app.h @@ -27,6 +27,7 @@ #include "cmdline_option.h" #include "file/path.h" +#include "signal_handler.h" extern void usage (); @@ -52,6 +53,8 @@ namespace MR extern const char* project_version; extern const char* build_date; + extern SignalHandler signal_handler; + const char* argtype_description (ArgType type); diff --git a/lib/formats/pipe.cpp b/lib/formats/pipe.cpp index 4415347661..487c524e91 100644 --- a/lib/formats/pipe.cpp +++ b/lib/formats/pipe.cpp @@ -13,12 +13,12 @@ * */ +#include "app.h" #include "file/utils.h" #include "file/path.h" #include "header.h" #include "image_io/pipe.h" #include "formats/list.h" -#include "signals/signals.h" namespace MR { @@ -40,9 +40,7 @@ namespace MR if (H.name().empty()) throw Exception ("no filename supplied to standard input (broken pipe?)"); -#ifndef MRTRIX_NO_SIGNAL_HANDLING - Signals::pipe_in = H.name(); -#endif + App::signal_handler += H.name(); if (!Path::has_suffix (H.name(), ".mif")) throw Exception ("MRtrix only supports the .mif format for command-line piping"); @@ -63,9 +61,7 @@ namespace MR H.name() = File::create_tempfile (0, "mif"); -#ifndef MRTRIX_NO_SIGNAL_HANDLING - Signals::pipe_out = H.name(); -#endif + App::signal_handler += H.name(); return mrtrix_handler.check (H, num_axes); } diff --git a/lib/image_io/pipe.cpp b/lib/image_io/pipe.cpp index 7aa2994f8c..478f1cb01b 100644 --- a/lib/image_io/pipe.cpp +++ b/lib/image_io/pipe.cpp @@ -55,7 +55,9 @@ namespace MR if (!is_new && files.size() == 1) { DEBUG ("deleting piped image file \"" + files[0].name + "\"..."); unlink (files[0].name.c_str()); + App::signal_handler -= files[0].name; } + } } diff --git a/lib/signal_handler.cpp b/lib/signal_handler.cpp new file mode 100644 index 0000000000..cbe3e510d4 --- /dev/null +++ b/lib/signal_handler.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include "signal_handler.h" + +#include +#include +#ifdef MRTRIX_SIGNAL_HANDLING +#include +#endif + +#include "exception.h" +#include "file/path.h" + +namespace MR +{ + + + + std::vector SignalHandler::data; + std::mutex SignalHandler::mutex; + + + + SignalHandler::SignalHandler() + { +#ifdef MRTRIX_SIGNAL_HANDLING + + // Construct the signal structure + struct sigaction act; + act.sa_handler = &handler; + // Since we're _Exit()-ing for any of these signals, block them all + sigfillset (&act.sa_mask); + act.sa_flags = 0; + + // Set the handler for a priori known signals only +#ifdef SIGALRM + sigaction (SIGALRM, &act, nullptr); +#endif +#ifdef SIGBUS + sigaction (SIGBUS, &act, nullptr); +#endif +#ifdef SIGFPE + sigaction (SIGFPE, &act, nullptr); +#endif +#ifdef SIGHUP + sigaction (SIGHUP, &act, nullptr); +#endif +#ifdef SIGILL + sigaction (SIGILL, &act, nullptr); +#endif +#ifdef SIGINT + sigaction (SIGINT, &act, nullptr); +#endif +#ifdef SIGPIPE + sigaction (SIGPIPE, &act, nullptr); +#endif +#ifdef SIGPWR + sigaction (SIGPWR, &act, nullptr); +#endif +#ifdef SIGQUIT + sigaction (SIGQUIT, &act, nullptr); +#endif +#ifdef SIGSEGV + sigaction (SIGSEGV, &act, nullptr); +#endif +#ifdef SIGSYS + sigaction (SIGSYS, &act, nullptr); +#endif +#ifdef SIGTERM + sigaction (SIGTERM, &act, nullptr); +#endif +#ifdef SIGXCPU + sigaction (SIGXCPU, &act, nullptr); +#endif +#ifdef SIGXFSZ + sigaction (SIGXFSZ, &act, nullptr); +#endif + +#endif + } + + + + + void SignalHandler::operator+= (const std::string& s) + { + std::lock_guard lock (mutex); + data.push_back (s); + } + + void SignalHandler::operator-= (const std::string& s) + { + std::lock_guard lock (mutex); + auto i = data.begin(); + while (i != data.end()) { + if (*i == s) + i = data.erase (i); + else + ++i; + } + } + + + + + void SignalHandler::handler (int i) noexcept + { + // Only show one signal error message if multi-threading + std::lock_guard lock (mutex); + // Don't attempt to use any terminal colouring + switch (i) { +#ifdef SIGALRM + case SIGALRM: + std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGALRM (" << SIGALRM << ")] Timer expiration\n"; + break; +#endif +#ifdef SIGBUS + case SIGBUS: + std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGBUS (" << SIGBUS << ")] Bus error: Accessing invalid address (out of storage space?)\n"; + break; +#endif +#ifdef SIGFPE + case SIGFPE: + std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGFPE (" << SIGFPE << ")] Floating-point arithmetic exception\n"; + break; +#endif +#ifdef SIGHUP + case SIGHUP: + std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGHUP (" << SIGHUP << ")] Disconnection of terminal\n"; + break; +#endif +#ifdef SIGILL + case SIGILL: + std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGILL (" << SIGILL << ")] Illegal instruction (corrupt binary command file?)\n"; + break; +#endif +#ifdef SIGINT + case SIGINT: + std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGINT (" << SIGINT << ")] Program manually interrupted by terminal\n"; + break; +#endif +#ifdef SIGPIPE + case SIGPIPE: + std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGPIPE (" << SIGPIPE << ")] Nothing on receiving end of pipe\n"; + break; +#endif +#ifdef SIGPWR + case SIGPWR: + std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGPWR (" << SIGPWR << ")] Power failure restart\n"; + break; +#endif +#ifdef SIGQUIT + case SIGQUIT: + std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGQUIT (" << SIGQUIT << ")] Received terminal quit signal\n"; + break; +#endif +#ifdef SIGSEGV + case SIGSEGV: + std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGSEGV (" << SIGSEGV << ")] Segmentation fault: Invalid memory reference\n"; + break; +#endif +#ifdef SIGSYS + case SIGSYS: + std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGSYS (" << SIGSYS << ")] Bad system call\n"; + break; +#endif +#ifdef SIGTERM + case SIGTERM: + std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGTERM (" << SIGTERM << ")] Terminated by kill command\n"; + break; +#endif +#ifdef SIGXCPU + case SIGXCPU: + std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGXCPU (" << SIGXCPU << ")] CPU time limit exceeded\n"; + break; +#endif +#ifdef SIGXFSZ + case SIGXFSZ: + std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGXFSZ (" << SIGXFSZ << ")] File size limit exceeded\n"; + break; +#endif + default: + std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: " << i << "] Unknown system signal\n"; + } + + for (auto i : data) { + if (Path::exists (i)) + // Don't use File::unlink: may throw an exception + ::unlink (i.c_str()); + } + std::_Exit (i); + } + + + +} diff --git a/lib/signals/signals.h b/lib/signal_handler.h similarity index 54% rename from lib/signals/signals.h rename to lib/signal_handler.h index 8d203cf2b1..1dd24e3078 100644 --- a/lib/signals/signals.h +++ b/lib/signal_handler.h @@ -13,32 +13,38 @@ * */ -#ifndef __signals_h__ -#define __signals_h__ - -#ifndef MRTRIX_NO_SIGNAL_HANDLING +#ifndef __signal_handler_h__ +#define __signal_handler_h__ +#include #include +#include namespace MR { - namespace Signals - { - std::string pipe_in, pipe_out; + class SignalHandler + { + public: + SignalHandler(); + SignalHandler (const SignalHandler&) = delete; - void init(); + void operator+= (const std::string&); + void operator-= (const std::string&); - void _handler (int) noexcept; - void _at_quick_exit() noexcept; + private: + static std::vector data; + static std::mutex mutex; + static void on_exit() noexcept; + static void handler (int) noexcept; + }; - } -} -#endif + +} #endif diff --git a/lib/signals/signals.cpp b/lib/signals/signals.cpp deleted file mode 100644 index e533fc7374..0000000000 --- a/lib/signals/signals.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - -#include "signals/signals.h" - -#include -#include -#include -#include - -#include "exception.h" -#include "signals/table.h" - -#ifndef MRTRIX_NO_SIGNAL_HANDLING - -namespace MR -{ - namespace Signals - { - - - - void init() - { - // Construct the signal structure - struct sigaction act; - act.sa_handler = &_handler; - sigemptyset (&act.sa_mask); -#ifdef SIGINT - sigaddset (&act.sa_mask, SIGINT); -#endif -#ifdef SIGQUIT - sigaddset (&act.sa_mask, SIGQUIT); -#endif - act.sa_flags = 0; - - // Set the handler for a priori known signals only - for (size_t i = 0; i != NSIG; ++i) { - if (table[i][0]) - sigaction (i, &act, nullptr); - } - - // Set function to delete temporary files if the signal handler is invoked - std::at_quick_exit (_at_quick_exit); - } - - - - void _handler (int i) - { - // Don't attempt to use any terminal colouring - std::cerr << App::NAME << ": [SYSTEM FATAL CODE: " << i << "] " << table[i] << "\n"; - std::quick_exit (i); - } - - - - void _at_quick_exit() - { - if (pipe_in.size()) - unlink (pipe_in.c_str()); - if (pipe_out.size()) - unlink (pipe_out.c_str()); - } - - - - } -} - -#endif diff --git a/lib/signals/table.h b/lib/signals/table.h deleted file mode 100644 index 3933277de6..0000000000 --- a/lib/signals/table.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - -#ifndef __signals_table_h__ -#define __signals_table_h__ - -namespace MR -{ - namespace Signals - { - - extern const char* table[]; - - } -} - -#endif From 788ffd325ba91e1afe52afa880d7638bb59c1e66 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 22 Aug 2016 15:16:26 +1000 Subject: [PATCH 092/723] MR::Connectome: Slight alterations - Use function is_directed() to detect directed matrices (i.e. not symmetric, not upper triangular, not lower triangular). - Define re-usable option group to affect connectome matrix contents on write. --- cmd/connectome2tck.cpp | 4 +- cmd/tck2connectome.cpp | 19 +++- docs/reference/commands/tck2connectome.rst | 9 +- src/connectome/connectome.h | 110 ++++++++++++++------- src/dwi/tractography/connectome/matrix.cpp | 15 --- src/dwi/tractography/connectome/matrix.h | 4 +- 6 files changed, 97 insertions(+), 64 deletions(-) diff --git a/cmd/connectome2tck.cpp b/cmd/connectome2tck.cpp index bb365d663e..418ca2dfdf 100644 --- a/cmd/connectome2tck.cpp +++ b/cmd/connectome2tck.cpp @@ -49,7 +49,7 @@ using namespace MR::DWI::Tractography::Connectome; const char* file_outputs[] = { "per_edge", "per_node", "single", NULL }; -const OptionGroup OutputOptions = OptionGroup ("Options for determining the content / format of output files") +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() @@ -99,7 +99,7 @@ void usage () OPTIONS - + OutputOptions + + TrackOutputOptions + TrackWeightsOptions; } diff --git a/cmd/tck2connectome.cpp b/cmd/tck2connectome.cpp index 6e991fd7d4..6f52632561 100644 --- a/cmd/tck2connectome.cpp +++ b/cmd/tck2connectome.cpp @@ -58,6 +58,7 @@ void usage () OPTIONS + MR::DWI::Tractography::Connectome::AssignmentOptions + MR::DWI::Tractography::Connectome::MetricOptions + + MR::Connectome::MatrixOutputOptions + OptionGroup ("Other options for tck2connectome") @@ -71,9 +72,6 @@ void usage () + Option ("out_assignments", "output the node assignments of each streamline to a file") + Argument ("path").type_file_out() - + Option ("zero_diagonal", "set all diagonal entries in the matrix to zero \n" - "(these represent streamlines that connect to the same node at both ends)") - + Option ("vector", "output a vector representing connectivities from a given seed point to target nodes, " "rather than a matrix of node-node connectivities"); @@ -155,10 +153,21 @@ void run () if (!get_options ("keep_unassigned").size()) connectome.remove_unassigned(); + MR::Connectome::matrix_type data = connectome.get(); + + // These only modify the matrix as it is written to file + if (get_options ("symmetric").size()) { + if (vector_output) { + WARN ("Option -symmetric not applicable when generating connectivity vector"); + } else { + MR::Connectome::to_symmetric (data); + } + } if (get_options ("zero_diagonal").size()) - connectome.zero_diagonal(); + data.matrix().diagonal().setZero(); + + save_matrix (data, argument[2]); - connectome.write (argument[2]); opt = get_options ("out_assignments"); if (opt.size()) connectome.write_assignments (opt[0][0]); diff --git a/docs/reference/commands/tck2connectome.rst b/docs/reference/commands/tck2connectome.rst index f83d154a7f..3ee6509737 100644 --- a/docs/reference/commands/tck2connectome.rst +++ b/docs/reference/commands/tck2connectome.rst @@ -46,6 +46,13 @@ Structural connectome metric options - **-scale_file path** scale each contribution to the connectome edge according to the values in a vector file +Options for outputting connectome matrices +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-symmetric** Make matrices symmetric on output + +- **-zero_diagonal** Set matrix diagonal to zero on output + Other options for tck2connectome ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -57,8 +64,6 @@ Other options for tck2connectome - **-out_assignments path** output the node assignments of each streamline to a file -- **-zero_diagonal** set all diagonal entries in the matrix to zero (these represent streamlines that connect to the same node at both ends) - - **-vector** output a vector representing connectivities from a given seed point to target nodes, rather than a matrix of node-node connectivities Standard options diff --git a/src/connectome/connectome.h b/src/connectome/connectome.h index 8a76d6fa04..40cf89b977 100644 --- a/src/connectome/connectome.h +++ b/src/connectome/connectome.h @@ -21,73 +21,107 @@ #include +#include "app.h" #include "exception.h" #include "mrtrix.h" #include "types.h" namespace MR { -namespace Connectome { + namespace Connectome { -typedef uint32_t node_t; + typedef uint32_t node_t; -typedef default_type value_type; -typedef Eigen::Array matrix_type; -typedef Eigen::Array vector_type; -typedef Eigen::Array mask_type; + typedef default_type value_type; + typedef Eigen::Array matrix_type; + typedef Eigen::Array vector_type; + typedef Eigen::Array mask_type; -template -void to_upper (MatrixType& in) -{ - if (in.rows() != in.cols()) - throw Exception ("Connectome matrix is not square (" + str(in.rows()) + " x " + str(in.cols()) + ")"); + extern const App::OptionGroup MatrixOutputOptions; - for (node_t row = 0; row != in.rows(); ++row) { - for (node_t col = row+1; col != in.cols(); ++col) { - const typename MatrixType::Scalar lower_value = in (col, row); - const typename MatrixType::Scalar upper_value = in (row, col); - if (upper_value && lower_value && (upper_value != lower_value)) - throw Exception ("Connectome matrix is not symmetrical"); + template + void check (const MatrixType& in, const node_t num_nodes) + { + if (in.rows() != in.cols()) + throw Exception ("Connectome matrix is not square (" + str(in.rows()) + " x " + str(in.cols()) + ")"); + if (in.rows() != num_nodes) + throw Exception ("Connectome matrix contains " + str(in.rows()) + " nodes; expected " + str(num_nodes)); + } - if (!upper_value && lower_value) - in (row, col) = lower_value; - in (col, row) = typename MatrixType::Scalar(0); - } } -} + template + bool is_directed (MatrixType& in) + { + if (in.rows() != in.cols()) + throw Exception ("Connectome matrix is not square (" + str(in.rows()) + " x " + str(in.cols()) + ")"); + for (node_t row = 0; row != in.rows(); ++row) { + for (node_t col = row+1; col != in.cols(); ++col) { + const typename MatrixType::Scalar lower_value = in (col, row); + const typename MatrixType::Scalar upper_value = in (row, col); -template -void check (const MatrixType& in, const node_t num_nodes) -{ - if (in.rows() != in.cols()) - throw Exception ("Connectome matrix is not square (" + str(in.rows()) + " x " + str(in.cols()) + ")"); - if (in.rows() != num_nodes) - throw Exception ("Connectome matrix contains " + str(in.rows()) + " nodes; expected " + str(num_nodes)); + if (upper_value && lower_value && (upper_value != lower_value)) + return true; - for (node_t row = 0; row != num_nodes; ++row) { - for (node_t col = row+1; col != num_nodes; ++col) { + } } - const typename MatrixType::Scalar lower_value = in (col, row); - const typename MatrixType::Scalar upper_value = in (row, col); + return false; + } - if (upper_value && lower_value && (upper_value != lower_value)) - throw Exception ("Connectome matrix is not symmetrical"); - } } -} + template + void to_symmetric (MatrixType& in) + { + if (is_directed (in)) + throw Exception ("Cannot convert a non-symmetric directed matrix to be symmetric"); + for (node_t row = 0; row != in.rows(); ++row) { + for (node_t col = row+1; col != in.cols(); ++col) { -} + const typename MatrixType::Scalar lower_value = in (col, row); + const typename MatrixType::Scalar upper_value = in (row, col); + + if (upper_value && !lower_value) + in (row, col) = in (col, row) = upper_value; + else if (lower_value && !upper_value) + in (row, col) = in (col, row) = lower_value; + + } } + } + + + + template + void to_upper (MatrixType& in) + { + if (is_directed (in)) + throw Exception ("Cannot convert a non-symmetric directed matrix to upper triangular"); + + for (node_t row = 0; row != in.rows(); ++row) { + for (node_t col = row+1; col != in.cols(); ++col) { + + const typename MatrixType::Scalar lower_value = in (col, row); + const typename MatrixType::Scalar upper_value = in (row, col); + + if (!upper_value && lower_value) + in (row, col) = lower_value; + in (col, row) = typename MatrixType::Scalar(0); + + } } + } + + + + } } diff --git a/src/dwi/tractography/connectome/matrix.cpp b/src/dwi/tractography/connectome/matrix.cpp index a287acde6f..aae2c277ee 100644 --- a/src/dwi/tractography/connectome/matrix.cpp +++ b/src/dwi/tractography/connectome/matrix.cpp @@ -170,15 +170,6 @@ void Matrix::remove_unassigned() -void Matrix::zero_diagonal() -{ - if (is_vector()) return; - for (node_t i = 0; i != data.rows(); ++i) - data (i, i) = counts (i, i) = 0.0; -} - - - void Matrix::error_check (const std::set& missing_nodes) { std::vector node_counts (data.cols(), 0); @@ -205,11 +196,6 @@ void Matrix::error_check (const std::set& missing_nodes) -void Matrix::write (const std::string& path) const -{ - MR::save_matrix (data, path); -} - void Matrix::write_assignments (const std::string& path) const { File::OFStream stream (path); @@ -226,7 +212,6 @@ void Matrix::write_assignments (const std::string& path) const - void Matrix::apply (double& target, const double value, const double weight) { switch (statistic) { diff --git a/src/dwi/tractography/connectome/matrix.h b/src/dwi/tractography/connectome/matrix.h index 4ba665abf9..d592fda8bd 100644 --- a/src/dwi/tractography/connectome/matrix.h +++ b/src/dwi/tractography/connectome/matrix.h @@ -62,15 +62,15 @@ class Matrix void finalize(); void remove_unassigned(); - void zero_diagonal(); void error_check (const std::set&); - void write (const std::string&) const; void write_assignments (const std::string&) const; bool is_vector() const { return (data.rows() == 1); } + const MR::Connectome::matrix_type get() const { return data; } + private: MR::Connectome::matrix_type data, counts; From bdb187c1a220014081503fa4035842a3864fe218 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 22 Aug 2016 15:22:50 +1000 Subject: [PATCH 093/723] File missing from previous commit --- src/connectome/connectome.cpp | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/connectome/connectome.cpp diff --git a/src/connectome/connectome.cpp b/src/connectome/connectome.cpp new file mode 100644 index 0000000000..354c67f9f7 --- /dev/null +++ b/src/connectome/connectome.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + + +#include "connectome/connectome.h" + + +namespace MR { + namespace Connectome { + + + + using namespace App; + const OptionGroup MatrixOutputOptions = OptionGroup ("Options for outputting connectome matrices") + + Option ("symmetric", "Make matrices symmetric on output") + + Option ("zero_diagonal", "Set matrix diagonal to zero on output"); + + + + } +} + + From 9d37edcb41a3b5921a93166fb2fa18948fc20a27 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 22 Aug 2016 15:23:15 +1000 Subject: [PATCH 094/723] Docs: FAQ: Additional info on updated interface for tck2connectome --- docs/tutorials/FAQ.rst | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/FAQ.rst b/docs/tutorials/FAQ.rst index 9fb6b97a15..73239423d4 100644 --- a/docs/tutorials/FAQ.rst +++ b/docs/tutorials/FAQ.rst @@ -208,11 +208,14 @@ of the location of those libraries; e.g.: (Replace the path to the *MRtrix3* scripts directory with the location of your own installation) -``tck2connectome`` no longer has the ``-contrast mean_scalar`` option...? +``tck2connectome`` no longer has the ``-contrast X`` option...? ------------------------------------------------------------------------- -The functionality previously provided by this command and option can now be -achieved by instead splitting the operation into two independent steps: +The functionalities previously provided by the ``-contrast`` option in +this command can still be achieved, but through more explicit steps: + +``tck2connectome -contrast mean_scalar`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code:: @@ -227,6 +230,19 @@ connectome construction to be the values from this file, and finally calculates the value of each edge to be the *mean of the values for the streamlines in that edge*. +``tck2connectome -contrast meanlength`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: + + tck2connectome tracks.tck nodes.mif connectome.csf -scale_length -stat_edge mean + +For each streamline, the contribution of that streamline to the relevant +edge is *scaled by the length* of that streamline; so, in the absence of any +other scaling, the contribution of that streamline will be equal to the length +of the streamline in mm. Finally, for each edge, take the *mean* of the values +contributed from all streamlines belonging to that edge. + Maximum spherical harmonic degree ``lmax`` ------------------------------------------ From 851d7d4b6615187c4761c4e9e739cfe33830e455 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 25 Aug 2016 12:49:27 +1000 Subject: [PATCH 095/723] Ported fixel2voxel to new fixel format --- cmd/fixel2sh.cpp | 2 +- cmd/fixel2voxel.cpp | 729 +++++++++++++------------------------ lib/fixel_format/helpers.h | 3 +- lib/fixel_format/loop.h | 77 ++++ 4 files changed, 338 insertions(+), 473 deletions(-) create mode 100644 lib/fixel_format/loop.h diff --git a/cmd/fixel2sh.cpp b/cmd/fixel2sh.cpp index 6f7c11172e..736c97b540 100644 --- a/cmd/fixel2sh.cpp +++ b/cmd/fixel2sh.cpp @@ -75,7 +75,7 @@ void run () std::vector sh_values; Eigen::Matrix apsf_values; - for (auto l1 = Loop ("converting fixel image to sherical harmonic image", in_index_image) (in_index_image, sh_image); l1; ++l1) { + 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); in_index_image.index(3) = 0; uint32_t num_fixels_in_voxel = in_index_image.value(); diff --git a/cmd/fixel2voxel.cpp b/cmd/fixel2voxel.cpp index fba75a877e..50ac55c7ae 100644 --- a/cmd/fixel2voxel.cpp +++ b/cmd/fixel2voxel.cpp @@ -21,26 +21,23 @@ #include "algo/loop.h" #include "algo/threaded_loop.h" -#include "sparse/fixel_metric.h" -#include "sparse/image.h" -#include "sparse/keys.h" +#include "dwi/tractography/file.h" +#include "dwi/tractography/scalar_file.h" #include "math/SH.h" +#include "fixel_format/helpers.h" +#include "fixel_format/keys.h" +#include "fixel_format/loop.h" using namespace MR; using namespace App; -using Sparse::FixelMetric; - const char* operations[] = { "mean", "sum", "product", - "rms", - "var", - "std", "min", "max", "absmax", @@ -50,8 +47,7 @@ const char* operations[] = { "sf", "dec_unit", "dec_scaled", - "split_size", - "split_value", + "split_data", "split_dir", NULL }; @@ -59,17 +55,17 @@ const char* operations[] = { void usage () { - AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; + AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au) & David Raffelt (david.raffelt@florey.edu.au)"; DESCRIPTION + "convert a fixel-based sparse-data image into some form of scalar image. " "This could be: \n" - "- Some statistic computed across all fixel values within a voxel: mean, sum, product, rms, var, std, min, max, absmax, magmax\n" + "- Some statistic computed across all fixel values within a voxel: mean, sum, product, min, max, absmax, magmax\n" "- The number of fixels in each voxel: count\n" "- Some measure of crossing-fibre organisation: complexity, sf ('single-fibre')\n" "- A 4D directionally-encoded colour image: dec_unit, dec_scaled\n" - "- A 4D scalar image with one 3D volume per fixel: split_size, split_value\n" - "- A 4D image with three 3D volumes per fixel direction: split_dir"; + "- A 4D scalar image of fixel values with one 3D volume per fixel: split_data\n" + "- A 4D image of fixel directions, stored as three 3D volumes per fixel direction: split_dir"; REFERENCES + "* Reference for 'complexity' operation:\n" @@ -78,559 +74,334 @@ void usage () "NeuroImage, 2014 (in press)"; ARGUMENTS - + Argument ("fixel_in", "the input sparse fixel image.").type_image_in () + + 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 ("weighted", "weight the contribution of each fixel to the per-voxel result according to its volume " - "(note that this option is not applicable for all operations, and should be avoided if the " - "value stored in the fixel image is itself the estimated fibre volume)"); + + Option ("weighted", "weight the contribution of each fixel to the per-voxel result according to its volume. " + "E.g. when estimating a voxel-based measure of mean axon diameter, a fixel's mean axon diameter " + "should be weigthed by its relative volume within the voxel. Note that AFD can be used as a psuedomeasure of fixel volume.") + + Argument ("fixel_in").type_image_in (); } -class OpBase +class Mean { - protected: - typedef Sparse::Image in_type; - typedef Image out_type; - public: - OpBase (const bool weighted) : - weighted (weighted) { } - OpBase (const OpBase& that) : - weighted (that.weighted) { } - virtual ~OpBase() { } - virtual bool operator() (in_type&, out_type&) = 0; - protected: - const bool weighted; -}; - + Mean (Image& data, Image& vol) : + data (data), vol (vol) {} -class Mean : public OpBase -{ - public: - Mean (const bool weighted) : - OpBase (weighted), - sum (0.0f), - sum_volumes (0.0f) { } - Mean (const Mean& that) : - OpBase (that), - sum (0.0f), - sum_volumes (0.0f) { } - bool operator() (in_type& in, out_type& out) + void operator() (Image& index, Image& out) { - sum = sum_volumes = 0.0; - if (weighted) { - for (size_t i = 0; i != in.value().size(); ++i) { - sum += in.value()[i].size * in.value()[i].value; - sum_volumes += in.value()[i].size; + default_type sum = 0.0; + default_type sum_volumes = 0.0; + if (vol.valid()) { + for (auto f = FixelFormat::FixelLoop (index) (data, vol); f; ++f) { + sum += data.value() * vol.value(); + sum_volumes += vol.value(); } out.value() = sum_volumes ? (sum / sum_volumes) : 0.0; } else { - for (size_t i = 0; i != in.value().size(); ++i) - sum += in.value()[i].value; - out.value() = in.value().size() ? (sum / default_type(in.value().size())) : 0.0; + index.index(3) = 0; + size_t num_fixels = index.value(); + for (auto f = FixelFormat::FixelLoop (index) (data); f; ++f) + sum += data.value(); + out.value() = num_fixels ? (sum / default_type(num_fixels)) : 0.0; } - return true; } - private: - default_type sum, sum_volumes; + protected: + Image data, vol; }; -class Sum : public OpBase -{ - public: - Sum (const bool weighted) : - OpBase (weighted), - sum (0.0) { } - Sum (const Sum& that) : - OpBase (that), - sum (0.0) { } - bool operator() (in_type& in, out_type& out) - { - sum = 0.0; - if (weighted) { - for (size_t i = 0; i != in.value().size(); ++i) - sum += in.value()[i].size * in.value()[i].value; - out.value() = sum; - } else { - for (size_t i = 0; i != in.value().size(); ++i) - sum += in.value()[i].value; - out.value() = sum; - } - return true; - } - private: - default_type sum; -}; -class Product : public OpBase +class Sum { public: - Product (const bool weighted) : - OpBase (weighted), - product (0.0) - { - if (weighted) - WARN ("Option -weighted has no meaningful interpretation for product operation; ignoring"); - } - Product (const Product& that) : - OpBase (that), - product (0.0) { } - bool operator() (in_type& in, out_type& out) - { - if (!in.value().size()) { - out.value() = 0.0; - return true; - } - product = in.value()[0].value; - for (size_t i = 1; i != in.value().size(); ++i) - product *= in.value()[i].value; - out.value() = product; - return true; - } - private: - default_type product; -}; + Sum (Image& data, Image& vol) : + data (data), vol (vol) {} -class RMS : public OpBase -{ - public: - RMS (const bool weighted) : - OpBase (weighted), - sum (0.0), - sum_volumes (0.0) { } - RMS (const RMS& that) : - OpBase (that), - sum (0.0), - sum_volumes (0.0) { } - bool operator() (in_type& in, out_type& out) + void operator() (Image& index, Image& out) { - sum = sum_volumes = 0.0; - if (weighted) { - for (size_t i = 0; i != in.value().size(); ++i) { - sum *= in.value()[i].size * Math::pow2 (in.value()[i].value); - sum_volumes += in.value()[i].size; - } - out.value() = std::sqrt (sum / sum_volumes); + + if (vol.valid()) { + for (auto f = FixelFormat::FixelLoop (index) (data, vol); f; ++f) + out.value() += data.value() * vol.value(); } else { - for (size_t i = 0; i != in.value().size(); ++i) - sum += Math::pow2 (in.value()[i].value); - out.value() = std::sqrt (sum / default_type(in.value().size())); + for (auto f = FixelFormat::FixelLoop (index) (data); f; ++f) + out.value() += data.value(); } - return true; } - private: - default_type sum, sum_volumes; + protected: + Image data, vol; }; -class Var : public OpBase + +class Product { public: - Var (const bool weighted) : - OpBase (weighted), - sum (0.0f), - weighted_mean (0.0), - sum_volumes (0.0), - sum_volumes_sqr (0.0), - sum_sqr (0.0) { } - Var (const Var& that) : - OpBase (that), - sum (0.0), - weighted_mean (0.0), - sum_volumes (0.0), - sum_volumes_sqr (0.0), - sum_sqr (0.0) { } - bool operator() (in_type& in, out_type& out) + Product (Image& data) : + data (data) {} + + void operator() (Image& index, Image& out) { - if (!in.value().size()) { - out.value() = NAN; - return true; - } else if (in.value().size() == 1) { + index.index(3) = 0; + uint32_t num_fixels = index.value(); + if (!num_fixels) { out.value() = 0.0; - return true; + return; } - if (weighted) { - sum = sum_volumes = 0.0; - for (size_t i = 0; i != in.value().size(); ++i) { - sum += in.value()[i].size * in.value()[i].value; - sum_volumes += in.value()[i].size; - } - weighted_mean = sum / sum_volumes; - sum = sum_volumes_sqr = 0.0; - for (size_t i = 0; i != in.value().size(); ++i) { - sum += in.value()[i].size * Math::pow2 (weighted_mean - in.value()[i].value); - sum_volumes_sqr += Math::pow2 (in.value()[i].size); - } - // Unbiased variance estimator in the presence of weighting - out.value() = sum / (sum_volumes - (sum_volumes_sqr / sum_volumes)); - } else { - sum = sum_sqr = 0.0; - for (size_t i = 0; i != in.value().size(); ++i) { - sum += in.value()[i].value; - sum_sqr += Math::pow2 (in.value()[i].value); - } - out.value() = (sum_sqr - (Math::pow2 (sum) / default_type(in.value().size()))) / default_type(in.value().size() - 1); + index.index(3) = 1; + uint32_t offset = index.value(); + data.index(0) = offset; + out.value() = data.value(); + for (size_t f = 1; f != num_fixels; ++f) { + data.index(0)++; + out.value() *= data.value(); } - return true; } - private: - // Used for both calculations - default_type sum; - // Used for weighted calculations - default_type weighted_mean, sum_volumes, sum_volumes_sqr; - // Used for unweighted calculations - default_type sum_sqr; + protected: + Image data; }; -class Std : public Var -{ - public: - Std (const bool weighted) : - Var (weighted) { } - Std (const Std& that) : - Var (that) { } - bool operator() (in_type& in, out_type& out) - { - Var::operator() (in, out); - out.value() = std::sqrt (out.value()); - return true; - } -}; -class Min : public OpBase +class Min { public: - Min (const bool weighted) : - OpBase (weighted), - min (std::numeric_limits::infinity()) - { - if (weighted) - WARN ("Option -weighted has no meaningful interpretation for min operation; ignoring"); - } - Min (const Min& that) : - OpBase (that), - min (std::numeric_limits::infinity()) { } - bool operator() (in_type& in, out_type& out) + Min (Image& data) : + data (data) {} + + void operator() (Image& index, Image& out) { - min = std::numeric_limits::infinity(); - for (size_t i = 0; i != in.value().size(); ++i) { - if (in.value()[i].value < min) - min = in.value()[i].value; + default_type min = std::numeric_limits::infinity(); + for (auto f = FixelFormat::FixelLoop (index) (data); f; ++f) { + if (data.value() < min) + min = data.value(); } out.value() = std::isfinite (min) ? min : NAN; - return true; } - private: - default_type min; + protected: + Image data; }; -class Max : public OpBase + +class Max { public: - Max (const bool weighted) : - OpBase (weighted), - max (-std::numeric_limits::infinity()) - { - if (weighted) - WARN ("Option -weighted has no meaningful interpretation for max operation; ignoring"); - } - Max (const Max& that) : - OpBase (that), - max (-std::numeric_limits::infinity()) { } - bool operator() (in_type& in, out_type& out) + Max (Image& data) : + data (data) {} + + void operator() (Image& index, Image& out) { - max = -std::numeric_limits::infinity(); - for (size_t i = 0; i != in.value().size(); ++i) { - if (in.value()[i].value > max) - max = in.value()[i].value; + default_type max = -std::numeric_limits::infinity(); + for (auto f = FixelFormat::FixelLoop (index) (data); f; ++f) { + if (data.value() > max) + max = data.value(); } out.value() = std::isfinite (max) ? max : NAN; - return true; } - private: - default_type max; + protected: + Image data; }; -class AbsMax : public OpBase + +class AbsMax { public: - AbsMax (const bool weighted) : - OpBase (weighted), - max (0.0f) - { - if (weighted) - WARN ("Option -weighted has no meaningful interpretation for absmax operation; ignoring"); - } - AbsMax (const AbsMax& that) : - OpBase (that), - max (0.0f) { } - bool operator() (in_type& in, out_type& out) + AbsMax (Image& data) : + data (data) {} + + void operator() (Image& index, Image& out) { - max = 0.0f; - for (size_t i = 0; i != in.value().size(); ++i) { - if (std::abs (in.value()[i].value) > max) - max = std::abs (in.value()[i].value); + default_type absmax = -std::numeric_limits::infinity(); + for (auto f = FixelFormat::FixelLoop (index) (data); f; ++f) { + if (std::abs (data.value()) > absmax) + absmax = std::abs (data.value()); } - out.value() = max; - return true; + out.value() = std::isfinite (absmax) ? absmax : 0.0; } - private: - default_type max; + protected: + Image data; }; -class MagMax : public OpBase + +class MagMax { public: - MagMax (const bool weighted) : - OpBase (weighted), - max (0.0) - { - if (weighted) - WARN ("Option -weighted has no meaningful interpretation for magmax operation; ignoring"); - } - MagMax (const AbsMax& that) : - OpBase (that), - max (0.0) { } - bool operator() (in_type& in, out_type& out) + MagMax (Image& data) : + data (data) {} + + void operator() (Image& index, Image& out) { - max = 0.0; - for (size_t i = 0; i != in.value().size(); ++i) { - if (std::abs (in.value()[i].value) > std::abs (max)) - max = in.value()[i].value; + default_type magmax = 0.0; + for (auto f = FixelFormat::FixelLoop (index) (data); f; ++f) { + if (std::abs (data.value()) > std::abs (magmax)) + magmax = data.value(); } - out.value() = max; - return true; + out.value() = std::isfinite (magmax) ? magmax : 0.0; } - private: - default_type max; + protected: + Image data; }; -class Count : public OpBase -{ - public: - Count (const bool weighted) : - OpBase (weighted) - { - if (weighted) - WARN ("Option -weighted has no meaningful interpretation for count operation; ignoring"); - } - Count (const Count& that) : - OpBase (that) { } - bool operator() (in_type& in, out_type& out) - { - out.value() = in.value().size(); - return true; - } -}; -class Complexity : public OpBase +class Complexity { public: - Complexity (const bool weighted) : - OpBase (weighted), - max (0.0), - sum (0.0) - { - if (weighted) - WARN ("Option -weighted has no meaningful interpretation for complexity operation; ignoring"); - } - Complexity (const Complexity& that) : - OpBase (that), - max (0.0f), - sum (0.0f) { } - bool operator() (in_type& in, out_type& out) + Complexity (Image& data) : + data (data) {} + + void operator() (Image& index, Image& out) { - if (in.value().size() <= 1) { + index.index(3) = 0; + uint32_t num_fixels = index.value(); + if (num_fixels <= 1) { out.value() = 0.0; - return true; + return; } - max = sum = 0.0; - for (size_t i = 0; i != in.value().size(); ++i) { - max = std::max (max, default_type(in.value()[i].value)); - sum += in.value()[i].value; + default_type max = 0.0; + default_type sum = 0.0; + for (auto f = FixelFormat::FixelLoop (index) (data); f; ++f) { + max = std::max (max, default_type(data.value())); + sum += data.value(); } - out.value() = (default_type(in.value().size()) / default_type(in.value().size()-1.0)) * (1.0 - (max / sum)); - return true; + out.value() = (default_type(num_fixels) / default_type(num_fixels-1.0)) * (1.0 - (max / sum)); } - private: - default_type max, sum; + protected: + Image data; }; -class SF : public OpBase + +class SF { public: - SF (const bool weighted) : - OpBase (weighted), - max (0.0), - sum (0.0) - { - if (weighted) - WARN ("Option -weighted has no meaningful interpretation for sf operation; ignoring"); - } - SF (const Complexity& that) : - OpBase (that), - max (0.0), - sum (0.0) { } - bool operator() (in_type& in, out_type& out) + SF (Image& data) : data (data) {} + + void operator() (Image& index, Image& out) { - max = sum = 0.0f; - for (size_t i = 0; i != in.value().size(); ++i) { - max = std::max (max, default_type(in.value()[i].value)); - sum += in.value()[i].value; + default_type max = 0.0; + default_type sum = 0.0; + for (auto f = FixelFormat::FixelLoop (index) (data); f; ++f) { + max = std::max (max, default_type(data.value())); + sum += data.value(); } - out.value() = sum ? (max / sum) : 0.0f; - return true; + out.value() = sum ? (max / sum) : 0.0; } - private: - default_type max, sum; + protected: + Image data; }; -class DEC_unit : public OpBase + +class DEC_unit { public: - DEC_unit (const bool weighted) : - OpBase (weighted), - sum_dec {0.0, 0.0, 0.0} { } - DEC_unit (const DEC_unit& that) : - OpBase (that), - sum_dec {0.0, 0.0, 0.0} { } - bool operator() (in_type& in, out_type& out) + DEC_unit (Image& data, Image& vol, Image& dir) : + data (data), vol (vol), dir (dir) {} + + void operator() (Image& index, Image& out) { - sum_dec = {0.0, 0.0, 0.0}; - if (weighted) { - for (size_t i = 0; i != in.value().size(); ++i) - sum_dec += Eigen::Vector3 (std::abs (in.value()[i].dir[0]), std::abs (in.value()[i].dir[1]), std::abs (in.value()[i].dir[2])) * in.value()[i].value * in.value()[i].size; + Eigen::Vector3 sum_dec = {0.0, 0.0, 0.0}; + if (vol.valid()) { + for (auto f = FixelFormat::FixelLoop (index) (data, vol, dir); f; ++f) + sum_dec += Eigen::Vector3 (std::abs (dir.row(1)[0]), std::abs (dir.row(1)[1]), std::abs (dir.row(1)[2])) * data.value() * vol.value(); } else { - for (size_t i = 0; i != in.value().size(); ++i) - sum_dec += Eigen::Vector3 (std::abs (in.value()[i].dir[0]), std::abs (in.value()[i].dir[1]), std::abs (in.value()[i].dir[2])) * in.value()[i].value; + for (auto f = FixelFormat::FixelLoop (index) (data, dir); f; ++f) + sum_dec += Eigen::Vector3 (std::abs (dir.row(1)[0]), std::abs (dir.row(1)[1]), std::abs (dir.row(1)[2])) * data.value(); } - sum_dec.normalize(); + 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))]; - return true; } - private: - Eigen::Vector3 sum_dec; + protected: + Image data, vol, dir; }; -class DEC_scaled : public OpBase + +class DEC_scaled { public: - DEC_scaled (const bool weighted) : - OpBase (weighted), - sum_dec {0.0, 0.0, 0.0}, - sum_volume (0.0), - sum_value (0.0) { } - DEC_scaled (const DEC_scaled& that) : - OpBase (that), - sum_dec {0.0, 0.0, 0.0}, - sum_volume (0.0), - sum_value (0.0) { } - bool operator() (in_type& in, out_type& out) + DEC_scaled (Image& data, Image& vol, Image& dir) : + data (data), vol (vol), dir (dir) {} + + void operator() (Image& index, Image& out) { - sum_dec = {0.0, 0.0, 0.0}; - sum_value = 0.0; - if (weighted) { - sum_volume = 0.0; - for (size_t i = 0; i != in.value().size(); ++i) { - sum_dec += Eigen::Vector3 (std::abs (in.value()[i].dir[0]), std::abs (in.value()[i].dir[1]), std::abs (in.value()[i].dir[2])) * in.value()[i].value * in.value()[i].size; - sum_volume += in.value()[i].size; - sum_value += in.value()[i].size * in.value()[i].value; + Eigen::Vector3 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 = FixelFormat::FixelLoop (index) (data, vol, dir); f; ++f) { + sum_dec += Eigen::Vector3 (std::abs (dir.row(1)[0]), std::abs (dir.row(1)[1]), std::abs (dir.row(1)[2])) * data.value() * vol.value(); + sum_volume += vol.value(); + sum_value += vol.value() * data.value(); } - sum_dec.normalize(); + if ((sum_dec.array() != 0.0).any()) + sum_dec.normalize(); sum_dec *= (sum_value / sum_volume); } else { - for (size_t i = 0; i != in.value().size(); ++i) { - sum_dec += Eigen::Vector3 (std::abs (in.value()[i].dir[0]), std::abs (in.value()[i].dir[1]), std::abs (in.value()[i].dir[2])) * in.value()[i].value; - sum_value += in.value()[i].value; + for (auto f = FixelFormat::FixelLoop (index) (data, dir); f; ++f) { + sum_dec += Eigen::Vector3 (std::abs (dir.row(1)[0]), std::abs (dir.row(1)[1]), std::abs (dir.row(1)[2])) * data.value(); + sum_value += data.value(); } - sum_dec.normalize(); + 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))]; - return true; } - private: - Eigen::Vector3 sum_dec; - default_type sum_volume, sum_value; + protected: + Image data, vol, dir; }; -class SplitSize : public OpBase -{ - public: - SplitSize (const bool weighted) : - OpBase (weighted) - { - if (weighted) - WARN ("Option -weighted has no meaningful interpretation for split_amp operation; ignoring"); - } - SplitSize (const SplitSize& that) : - OpBase (that) { } - bool operator() (in_type& in, out_type& out) - { - for (out.index(3) = 0; out.index(3) < out.size(3); ++out.index(3)) { - if (out.index(3) < in.value().size()) - out.value() = in.value()[out.index(3)].size; - else - out.value() = 0.0; - } - return true; - } -}; -class SplitValue : public OpBase +class SplitData { public: - SplitValue (const bool weighted) : - OpBase (weighted) - { - if (weighted) - WARN ("Option -weighted has no meaningful interpretation for split_value operation; ignoring"); - } - SplitValue (const SplitValue& that) : - OpBase (that) { } - bool operator() (in_type& in, out_type& out) + SplitData (Image& data) : data (data) {} + + void operator() (Image& index, Image& out) { + index.index(3) = 0; + int num_fixels = (int)index.value(); + index.index(3) = 1; + uint32_t offset = index.value(); for (out.index(3) = 0; out.index(3) < out.size(3); ++out.index(3)) { - if (out.index(3) < in.value().size()) - out.value() = in.value()[out.index(3)].value; - else + if (out.index(3) < num_fixels) { + data.index(0) = offset + out.index(3); + out.value() = data.value(); + } else { out.value() = 0.0; + } } - return true; } + protected: + Image data; }; -class SplitDir : public OpBase + +class SplitDir { public: - SplitDir (const bool weighted) : - OpBase (weighted) - { - if (weighted) - WARN ("Option -weighted has no meaningful interpretation for split_dir operation; ignoring"); - } - SplitDir (const SplitDir& that) : - OpBase (that) { } - bool operator() (in_type& in, out_type& out) + SplitDir (Image& dir) : dir (dir) {} + + void operator() (Image& index, Image& out) { - size_t index; out.index(3) = 0; - for (index = 0; index != in.value().size(); ++index) { - for (size_t axis = 0; axis != 3; ++axis) { - out.value() = in.value()[index].dir[axis]; + for (auto f = FixelFormat::FixelLoop (index) (dir); f; ++f) { + for (size_t axis = 0; axis < 3; ++axis) { + dir.index(1) = axis; + out.value() = dir.value(); ++out.index(3); } } - for (; out.index(3) != out.size(3); ++out.index(3)) - out.value() = NAN; - return true; + for (; out.index(3) < out.size(3); ++out.index(3)) + out.value() = 0.0; } + protected: + Image dir; }; @@ -639,61 +410,77 @@ class SplitDir : public OpBase - void run () { - Header H_in = Header::open (argument[0]); - Sparse::Image in (H_in); + auto in_data = FixelFormat::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)"); + + Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_folder (argument[0])); + auto in_index_image = in_index_header.get_image(); + + Image in_directions; const int op = argument[1]; - Header H_out (H_in); + Header H_out (in_index_header); H_out.datatype() = DataType::Float32; H_out.datatype().set_byte_order_native(); - H_out.keyval().erase (Sparse::name_key); - H_out.keyval().erase (Sparse::size_key); - if (op == 10) { // count + H_out.keyval().erase (FixelFormat::n_fixels_key); + if (op == 7) { // count H_out.datatype() = DataType::UInt8; - } else if (op == 13 || op == 14) { // dec + } else if (op == 10 || op == 11) { // dec H_out.ndim() = 4; H_out.size (3) = 3; - } else if (op == 15 || op == 16 || op == 17) { // split_* + } else if (op == 12 || op == 13) { // split_* H_out.ndim() = 4; uint32_t max_count = 0; - for (auto l = Loop ("determining largest fixel count", in) (in); l; ++l) - max_count = std::max (max_count, in.value().size()); + for (auto l = Loop ("determining largest fixel count", in_index_image, 0, 3) (in_index_image); l; ++l) + max_count = std::max (max_count, (uint32_t)in_index_image.value()); if (max_count == 0) throw Exception ("fixel image is empty"); + // 3 volumes per fixel if performing split_dir - H_out.size(3) = (op == 17) ? (3 * max_count) : max_count; + H_out.size(3) = (op == 13) ? (3 * max_count) : max_count; } - auto out = Image::create (argument[2], H_out); + if (op == 10 || op == 11 || op == 13) // dec or split_dir + in_directions = FixelFormat::find_directions_header ( + FixelFormat::get_fixel_folder (in_data.name()), + in_index_header).get_image().with_direct_io(); + Image in_vol; auto opt = get_options ("weighted"); - const bool weighted = opt.size(); + if (opt.size()) + in_vol = Image::open(opt[0][0]); + + if (op == 2 || op == 3 || op == 4 || op == 5 || op == 6 || + op == 7 || op == 8 || op == 9 || op == 12 || op == 13) { + if (in_vol.valid()) + WARN ("Option -weighted has no meaningful interpretation for the operation specified; ignoring"); + } + + auto out = Image::create (argument[2], H_out); - auto loop = ThreadedLoop ("converting sparse fixel data to scalar image", in); + auto loop = ThreadedLoop ("converting sparse fixel data to scalar image", in_index_image, 0, 3); switch (op) { - case 0: loop.run (Mean (weighted), in, out); break; - case 1: loop.run (Sum (weighted), in, out); break; - case 2: loop.run (Product (weighted), in, out); break; - case 3: loop.run (RMS (weighted), in, out); break; - case 4: loop.run (Var (weighted), in, out); break; - case 5: loop.run (Std (weighted), in, out); break; - case 6: loop.run (Min (weighted), in, out); break; - case 7: loop.run (Max (weighted), in, out); break; - case 8: loop.run (AbsMax (weighted), in, out); break; - case 9: loop.run (MagMax (weighted), in, out); break; - case 10: loop.run (Count (weighted), in, out); break; - case 11: loop.run (Complexity (weighted), in, out); break; - case 12: loop.run (SF (weighted), in, out); break; - case 13: loop.run (DEC_unit (weighted), in, out); break; - case 14: loop.run (DEC_scaled (weighted), in, out); break; - case 15: loop.run (SplitSize (weighted), in, out); break; - case 16: loop.run (SplitValue (weighted), in, out); break; - case 17: loop.run (SplitDir (weighted), in, out); break; + case 0: loop.run (Mean (in_data, in_vol), in_index_image, out); break; + case 1: loop.run (Sum (in_data, in_vol), in_index_image, out); break; + case 2: loop.run (Product (in_data), in_index_image, out); break; + case 3: loop.run (Min (in_data), in_index_image, out); break; + case 4: loop.run (Max (in_data), in_index_image, out); break; + case 5: loop.run (AbsMax (in_data), in_index_image, out); break; + case 6: loop.run (MagMax (in_data), 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), in_index_image, out); break; + case 9: loop.run (SF (in_data), in_index_image, out); break; + case 10: loop.run (DEC_unit (in_data, in_vol, in_directions), in_index_image, out); break; + case 11: loop.run (DEC_scaled (in_data, in_vol, in_directions), in_index_image, out); break; + case 12: loop.run (SplitData (in_data), in_index_image, out); break; + case 13: loop.run (SplitDir (in_directions), in_index_image, out); break; } } diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index cf2a36b9d1..cf7eaa7202 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -201,7 +201,7 @@ namespace MR //! then it is checked to make sure it's the same as the input (based on the number of fixels it contains) inline void copy_fixel_file (const std::string& input_file_path, const std::string &output_folder, const bool check_existing_output = false) { check_fixel_folder (output_folder, true); - std::string output_path = Path::join (output_folder, Path::basename (Path::basename(input_file_path))); + std::string output_path = Path::join (output_folder, Path::basename (input_file_path)); Header input_header = Header::open (input_file_path); // do not need to copy if the file already exists and has the same number of fixels @@ -265,6 +265,7 @@ namespace MR return in_data_image; } + } } diff --git a/lib/fixel_format/loop.h b/lib/fixel_format/loop.h new file mode 100644 index 0000000000..42d7f4b21e --- /dev/null +++ b/lib/fixel_format/loop.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __sparse_loop_h__ +#define __sparse_loop_h__ + +#include "formats/mrtrix_utils.h" + +namespace MR +{ + namespace FixelFormat { + + + namespace { + struct set_offset { + FORCE_INLINE set_offset (u_int32_t offset) : offset (offset) { } + template + FORCE_INLINE void operator() (DataType& data) { data.index(0) = offset; } + u_int32_t offset; + }; + + struct inc_fixel { + template + FORCE_INLINE void operator() (DataType& data) { ++data.index(0); } + }; + } + + struct LoopFixelsInVoxel { + const size_t num_fixels; + const uint32_t offset; + + template + struct Run { + const size_t num_fixels; + const uint32_t offset; + uint32_t fixel_index; + const std::tuple data; + FORCE_INLINE Run (const size_t num_fixels, const uint32_t offset, const std::tuple& data) : + num_fixels (num_fixels), offset (offset), fixel_index (0), data (data) { + apply (set_offset (offset), data); + } + FORCE_INLINE operator bool() const { return fixel_index < num_fixels; } + FORCE_INLINE void operator++() { apply (inc_fixel (), data); fixel_index++; } + FORCE_INLINE void operator++(int) const { operator++(); } + }; + + template + FORCE_INLINE Run operator() (DataType&... data) const { return { num_fixels, offset, std::tie (data...) }; } + + }; + + template + FORCE_INLINE LoopFixelsInVoxel + FixelLoop (IndexType& index) { + index.index(3) = 0; + size_t num_fixels = index.value(); + index.index(3) = 1; + uint32_t offset = index.value(); + return { num_fixels, offset }; + } + + } +} + +#endif From b78c7c23d76d8bdd7b1c6feac977bf08c407f76d Mon Sep 17 00:00:00 2001 From: Max Pietsch Date: Mon, 29 Aug 2016 19:45:56 +0100 Subject: [PATCH 096/723] registration: inplace gaussian filter: reduced memory consumption, uses eigen, 2x speedup on hcp data --- lib/adapter/gaussian1D_buffered.h | 139 +++++++++++++++++++++++ lib/filter/smooth.h | 48 +++++++- src/registration/multi_resolution_lmax.h | 7 +- 3 files changed, 184 insertions(+), 10 deletions(-) create mode 100644 lib/adapter/gaussian1D_buffered.h diff --git a/lib/adapter/gaussian1D_buffered.h b/lib/adapter/gaussian1D_buffered.h new file mode 100644 index 0000000000..fcc98f5fb2 --- /dev/null +++ b/lib/adapter/gaussian1D_buffered.h @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __image_adapter_gaussian1D_buffered_h__ +#define __image_adapter_gaussian1D_buffered_h__ + +#include "adapter/base.h" + +namespace MR +{ + namespace Adapter + { + + template + class Gaussian1DBuffered : public Base { + public: + Gaussian1DBuffered (const ImageType& parent, + default_type stdev_in = 1.0, + size_t axis_in = 0, + size_t extent = 0, + bool zero_boundary = false) : + Base (parent), + stdev (stdev_in), + axis (axis_in), + zero_boundary (zero_boundary), + pos_prev (std::numeric_limits::max()) { + if (!extent) + radius = ceil(2 * stdev / spacing(axis)); + else if (extent == 1) + radius = 0; + else + radius = (extent - 1) / 2; + compute_kernel(); + buffer_size = size(axis); + buffer.resize(buffer_size); + } + + typedef typename ImageType::value_type value_type; + + value_type value () + { + if (!kernel.size()) + return Base::value(); + + const ssize_t pos = index (axis); + assert (pos_prev != pos && "Loop axis has to be equal to smoothing axis"); + + // fill buffer for current image line if necessary + if (pos == 0) { + assert (pos == 0); + for (ssize_t k = 0; k < buffer_size; ++k) { + index(axis) = k; + buffer(k) = Base::value(); + } + index (axis) = pos; + } else { + assert (pos_prev + 1 == pos && "Loop not contiguous along smoothing axis"); + } + + if (zero_boundary) + if (pos == 0 || pos == size(axis) - 1) + return 0.0; + + const ssize_t from = (pos < radius) ? 0 : pos - radius; + const ssize_t to = (pos + radius) >= size(axis) ? size(axis) - 1 : pos + radius; + + ssize_t c = (pos < radius) ? radius - pos : 0; + ssize_t kernel_size = to - from + 1; + + value_type result = kernel.segment(c, kernel_size).dot(buffer.segment(from, kernel_size)); + + if (!std::isfinite(result)) { + result = 0.0; + value_type av_weights = 0.0; + for (ssize_t k = from; k <= to; ++k, ++c) { + value_type neighbour_value = buffer(k); + if (std::isfinite (neighbour_value)) { + av_weights += kernel[c]; + result += neighbour_value * kernel[c]; + } + } + result /= av_weights; + } else if (kernel_size != kernel.size()) + result /= kernel.segment(c, kernel_size).sum(); + + pos_prev = pos; + + return result; + } + + using Base::name; + using Base::size; + using Base::spacing; + using Base::index; + + protected: + + void compute_kernel() + { + if ((radius < 1) || stdev <= 0.0) + return; + kernel.resize (2 * radius + 1); + default_type norm_factor = 0.0; + for (size_t c = 0; c < kernel.size(); ++c) { + kernel[c] = exp(-((c-radius) * (c-radius) * spacing(axis) * spacing(axis)) / (2 * stdev * stdev)); + norm_factor += kernel[c]; + } + for (size_t c = 0; c < kernel.size(); c++) { + kernel[c] /= norm_factor; + } + } + + default_type stdev; + ssize_t radius; + size_t axis; + Eigen::VectorXd kernel; + const bool zero_boundary; + ssize_t pos_prev; + ssize_t buffer_size; + Eigen::VectorXd buffer; + }; + } +} + + +#endif + diff --git a/lib/filter/smooth.h b/lib/filter/smooth.h index cd73e6ed69..3193989d1e 100644 --- a/lib/filter/smooth.h +++ b/lib/filter/smooth.h @@ -1,16 +1,16 @@ /* * Copyright (c) 2008-2016 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/ - * + * * MRtrix is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * + * * For more details, see www.mrtrix.org - * + * */ #ifndef __image_filter_gaussian_h__ @@ -21,6 +21,7 @@ #include "algo/copy.h" #include "algo/threaded_copy.h" #include "adapter/gaussian1D.h" +#include "adapter/gaussian1D_buffered.h" #include "filter/base.h" namespace MR @@ -51,6 +52,7 @@ namespace MR Base (in), extent (3, 0), stdev (3, 0.0), + stride_order (Stride::order (in)), zero_boundary (false) { for (int i = 0; i < 3; i++) @@ -62,7 +64,8 @@ namespace MR Smooth (const HeaderType& in, const std::vector& stdev_in): Base (in), extent (3, 0), - stdev (3, 0.0) + stdev (3, 0.0), + stride_order (Stride::order (in)) { set_stdev (stdev_in); datatype() = DataType::Float32; @@ -101,7 +104,7 @@ namespace MR //! This must be set as a single value to be used for the first 3 dimensions //! or separate values, one for each dimension. (Default: 1 voxel) void set_stdev (const std::vector& std_dev) - { + { for (size_t i = 0; i < std_dev.size(); ++i) if (stdev[i] < 0.0) throw Exception ("the Gaussian stdev values cannot be negative"); @@ -135,6 +138,7 @@ namespace MR for (size_t dim = 0; dim < 3; dim++) { if (stdev[dim] > 0) { + DEBUG ("creating scratch image for smoothing image along dimension " + str(dim)); out = std::make_shared > (Image::scratch (input)); Adapter::Gaussian1D > gaussian (*in, stdev[dim], dim, extent[dim], zero_boundary); threaded_copy (gaussian, *out, 0, input.ndim(), 2); @@ -146,9 +150,41 @@ namespace MR threaded_copy (*in, output); } + //! Smooth the image in place + template + void operator() (ImageType& in_and_output) + { + std::unique_ptr progress; + if (message.size()) { + size_t axes_to_smooth = 0; + for (std::vector::const_iterator i = stdev.begin(); i != stdev.end(); ++i) + if (*i) + ++axes_to_smooth; + progress.reset (new ProgressBar (message, axes_to_smooth + 1)); + } + + for (size_t dim = 0; dim < 3; dim++) { + if (stdev[dim] > 0) { + Adapter::Gaussian1DBuffered gaussian (in_and_output, stdev[dim], dim, extent[dim], zero_boundary); + std::vector axes (in_and_output.ndim(), dim); + size_t axdim = 1; + for (size_t i = 0; i < in_and_output.ndim(); ++i) { + if (stride_order[i] == dim) + continue; + axes[axdim++] = stride_order[i]; + } + DEBUG ("smoothing dimension " + str(dim) + " in place with stride order: " + str(axes)); + threaded_copy (gaussian, in_and_output, axes, 1); + if (progress) + ++(*progress); + } + } + } + protected: std::vector extent; std::vector stdev; + const std::vector stride_order; bool zero_boundary; }; //! @} diff --git a/src/registration/multi_resolution_lmax.h b/src/registration/multi_resolution_lmax.h index e94c0ab926..f2b6a581f7 100644 --- a/src/registration/multi_resolution_lmax.h +++ b/src/registration/multi_resolution_lmax.h @@ -19,7 +19,6 @@ #include "adapter/subset.h" #include "filter/smooth.h" - namespace MR { namespace Registration @@ -38,17 +37,17 @@ namespace MR if (do_reorientation) size[3] = Math::SH::NforL (lmax); Adapter::Subset subset (input, from, size); - Filter::Smooth smooth_filter (subset); std::vector stdev(3); for (size_t dim = 0; dim < 3; ++dim) stdev[dim] = input.spacing(dim) / (2.0 * scale_factor); smooth_filter.set_stdev (stdev); + DEBUG ("creating scratch image for smoothing input image..."); auto smoothed = ImageType::scratch (smooth_filter); - + threaded_copy (subset, smoothed); DEBUG ("smoothing input image based on scale factor..."); - smooth_filter (subset, smoothed); + smooth_filter (smoothed); return smoothed; } } From c9298d9329af46e1948c325e60272043881f6353 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 1 Sep 2016 16:42:15 +1000 Subject: [PATCH 097/723] Initial port of fixelcfestats to new fixel format. Removed fixel correspondence, since this now is a required preprocessing step. Not yet tested --- cmd/fixelcfestats.cpp | 251 +++++++++++++++------------------ lib/fixel_format/loop.h | 4 +- lib/math/stats/permutation.cpp | 8 +- lib/math/stats/permutation.h | 2 +- src/stats/cfe.cpp | 18 +-- src/stats/cfe.h | 12 +- 6 files changed, 136 insertions(+), 159 deletions(-) diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 53ea0abdcb..84efa98d12 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -1,16 +1,16 @@ /* * Copyright (c) 2008-2016 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/ - * + * * MRtrix is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * + * * For more details, see www.mrtrix.org - * + * */ #include "command.h" @@ -19,9 +19,9 @@ #include "algo/loop.h" #include "transform.h" #include "image.h" -#include "sparse/fixel_metric.h" -#include "sparse/keys.h" -#include "sparse/image.h" +#include "fixel_format/helpers.h" +#include "fixel_format/keys.h" +#include "fixel_format/loop.h" #include "math/stats/glm.h" #include "math/stats/permutation.h" #include "math/stats/typedefs.h" @@ -39,7 +39,6 @@ using namespace MR; using namespace App; using namespace MR::DWI::Tractography::Mapping; using namespace MR::Math::Stats; -using Sparse::FixelMetric; using Stats::CFE::direction_type; using Stats::CFE::connectivity_value_type; @@ -47,7 +46,7 @@ using Stats::CFE::connectivity_value_type; #define DEFAULT_CFE_E 2.0 #define DEFAULT_CFE_H 3.0 #define DEFAULT_CFE_C 0.5 -#define DEFAULT_ANGLE_THRESHOLD 30.0 +#define DEFAULT_ANGLE_THRESHOLD 45.0 #define DEFAULT_CONNECTIVITY_THRESHOLD 0.01 #define DEFAULT_SMOOTHING_STD 10.0 @@ -69,10 +68,10 @@ void usage () "NeuroImage, 2011, 54(3), 2006-19\n" ; ARGUMENTS - + Argument ("input", "a text file listing the file names of the input fixel images").type_file_in () + + Argument ("in_fixel_folder", "the fixel folder containing the data files for each subject (after obtaining fixel correspondence").type_file_in () - + Argument ("template", "the fixel mask used to define fixels of interest. This can be generated by " - "thresholding the group average AFD fixel image.").type_image_in () + + Argument ("subjects", "a text file listing the subject identifiers (one per line). This should correspond with the filenames " + "in the fixel folder (including the file extension), and be listed in the same order as the rows of the design matrix.").type_image_in () + Argument ("design", "the design matrix. Note that a column of 1's will need to be added for correlations.").type_file_in () @@ -80,7 +79,7 @@ void usage () + Argument ("tracks", "the tracks used to determine fixel-fixel connectivity").type_tracks_in () - + Argument ("output", "the filename prefix for all output.").type_text(); + + Argument ("out_fixel_folder", "the output folder where results will be saved. Will be created if it does not exist").type_text(); OPTIONS @@ -106,14 +105,14 @@ void usage () + Option ("negative", "automatically test the negative (opposite) contrast. By computing the opposite contrast simultaneously " "the computation time is reduced.") - + 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 ("smooth", "smooth the fixel value along the fibre tracts using a Gaussian kernel with the supplied FWHM (default: " + str(DEFAULT_SMOOTHING_STD, 2) + "mm)") + + Argument ("FWHM").type_float (0.0, 200.0) + Option ("connectivity", "a threshold to define the required fraction of shared connections to be included in the neighbourhood (default: " + str(DEFAULT_CONNECTIVITY_THRESHOLD, 2) + ")") + Argument ("threshold").type_float (0.0, 1.0) - + Option ("smooth", "smooth the fixel value along the fibre tracts using a Gaussian kernel with the supplied FWHM (default: " + str(DEFAULT_SMOOTHING_STD, 2) + "mm)") - + Argument ("FWHM").type_float (0.0, 200.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); } @@ -122,19 +121,13 @@ void usage () template void write_fixel_output (const std::string& filename, const VectorType& data, - const Header& header, - Sparse::Image& mask_vox, - Image& indexer_vox) + const Header& header) { - Sparse::Image output (filename, header); - for (auto i = Loop (mask_vox)(mask_vox, indexer_vox, output); i; ++i) { - output.value().set_size (mask_vox.value().size()); - indexer_vox.index(3) = 0; - int32_t index = indexer_vox.value(); - for (size_t f = 0; f != mask_vox.value().size(); ++f, ++index) { - output.value()[f] = mask_vox.value()[f]; - output.value()[f].value = data[index]; - } + assert (data.size() = header.size(2)); + auto output = Image::create (filename, header); + for (uint32_t i = 0; i < data.size(); ++i) { + output.index(2) = i; + output.value() = data[i]; } } @@ -144,44 +137,68 @@ void run() { auto opt = get_options ("negative"); bool compute_negative_contrast = opt.size() ? true : false; - 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 int num_perms = get_option_value ("nperms", DEFAULT_NUMBER_PERMUTATIONS); + const value_type smooth_std_dev = get_option_value ("smooth", DEFAULT_SMOOTHING_STD) / 2.3548; + const value_type connectivity_threshold = get_option_value ("connectivity", DEFAULT_CONNECTIVITY_THRESHOLD); + const bool do_nonstationary_adjustment = get_options ("nonstationary").size(); + const int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY); const value_type angular_threshold = get_option_value ("angle", DEFAULT_ANGLE_THRESHOLD); - const value_type angular_threshold_dp = cos (angular_threshold * (Math::pi/180.0)); - const value_type connectivity_threshold = get_option_value ("connectivity", DEFAULT_CONNECTIVITY_THRESHOLD); - const value_type smooth_std_dev = get_option_value ("smooth", DEFAULT_SMOOTHING_STD) / 2.3548; + const std::string input_fixel_folder = argument[0]; + Header index_header = FixelFormat::find_index_header (input_fixel_folder); + auto index_image = index_header.get_image(); + uint32_t num_fixels = std::stoul (index_image.keyval().at(FixelFormat::n_fixels_key)); - const bool do_nonstationary_adjustment = get_options ("nonstationary").size(); + std::vector positions (num_fixels); + std::vector directions (num_fixels); - const int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY); - - // Read filenames - std::vector filenames; { - std::string folder = Path::dirname (argument[0]); - std::ifstream ifs (argument[0].c_str()); + auto directions_data = FixelFormat::find_directions_header (input_fixel_folder, index_image).get_image().with_direct_io(); + + Transform image_transform (index_image); + for (auto i = Loop (index_image)(index_image); i; ++i) { + const Eigen::Vector3 vox ((default_type)index_image.index(0), (default_type)index_image.index(1), (default_type)index_image.index(2)); + index_image.index(3) = 1; + uint32_t offset = index_image.value(); + size_t fixel_index = 0; + for (auto f = FixelFormat::FixelLoop (index_image) (directions_data); f; ++f, ++fixel_index) { + directions[offset + fixel_index] = directions_data.row(1).cast(); + positions[offset + fixel_index] = image_transform.voxel2scanner * vox; + } + } + } + CONSOLE ("number of fixels: " + str(num_fixels)); + + + // Read identifiers and check files exist + std::vector identifiers; + Header header; + { + std::ifstream ifs (argument[1].c_str()); std::string temp; while (getline (ifs, temp)) { - std::string filename (Path::join (folder, temp)); + std::string filename (Path::join (input_fixel_folder, temp)); size_t p = filename.find_last_not_of(" \t"); if (std::string::npos != p) filename.erase(p+1); if (!MR::Path::exists (filename)) throw Exception ("input fixel image not found: " + filename); - filenames.push_back (filename); + header = Header::open (filename); + FixelFormat::fixels_match (index_header, header); + identifiers.push_back (filename); } } // Load design matrix: const matrix_type design = load_matrix (argument[2]); - if (design.rows() != (ssize_t)filenames.size()) + if (design.rows() != (ssize_t)identifiers.size()) throw Exception ("number of input files does not match number of rows in design matrix"); + // Load contrast matrix: const matrix_type contrast = load_matrix (argument[3]); @@ -190,42 +207,15 @@ void run() { if (contrast.rows() > 1) throw Exception ("only a single contrast vector (defined as a row) is currently supported"); - auto input_header = Header::open (argument[1]); - Sparse::Image mask_fixel_image (argument[1]); - - // Create an image to store the fixel indices, if we had a fixel scratch buffer this would be cleaner - Header index_header (input_header); - index_header.ndim() = 4; - index_header.size(3) = 2; - auto fixel_index_image = Image::scratch (index_header); - for (auto i = Loop ()(fixel_index_image);i; ++i) - fixel_index_image.value() = -1; - - std::vector positions; - std::vector directions; - - Transform image_transform (mask_fixel_image); - for (auto i = Loop (mask_fixel_image)(mask_fixel_image, fixel_index_image); i; ++i) { - fixel_index_image.index(3) = 0; - fixel_index_image.value() = directions.size(); - int32_t fixel_count = 0; - for (size_t f = 0; f != mask_fixel_image.value().size(); ++f, ++fixel_count) { - directions.push_back (mask_fixel_image.value()[f].dir.cast()); - const Eigen::Vector3 pos (mask_fixel_image.index(0), mask_fixel_image.index(1), mask_fixel_image.index(2)); - positions.push_back (image_transform.voxel2scanner * pos); - } - fixel_index_image.index(3) = 1; - fixel_index_image.value() = fixel_count; - } - - const uint32_t num_fixels = directions.size(); - CONSOLE ("number of fixels: " + str(num_fixels)); + // Copy index and directions over to output folder + const std::string output_fixel_folder = argument[5]; + FixelFormat::copy_index_file (input_fixel_folder, output_fixel_folder); + FixelFormat::copy_directions_file (input_fixel_folder, output_fixel_folder); // Compute fixel-fixel connectivity - std::vector > connectivity_matrix (num_fixels); + std::vector > connectivity_matrix (num_fixels); std::vector fixel_TDI (num_fixels, 0.0); const std::string track_filename = argument[4]; - const std::string output_prefix = argument[5]; DWI::Tractography::Properties properties; DWI::Tractography::Reader<> track_file (track_filename, properties); // Read in tracts, and compute whole-brain fixel-fixel connectivity @@ -237,10 +227,10 @@ void run() { { typedef DWI::Tractography::Mapping::SetVoxelDir SetVoxelDir; DWI::Tractography::Mapping::TrackLoader loader (track_file, num_tracks, "pre-computing fixel-fixel connectivity"); - DWI::Tractography::Mapping::TrackMapperBase mapper (input_header); - mapper.set_upsample_ratio (DWI::Tractography::Mapping::determine_upsample_ratio (input_header, properties, 0.333f)); + 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); - Stats::CFE::TrackProcessor tract_processor (fixel_index_image, directions, fixel_TDI, connectivity_matrix, angular_threshold); + Stats::CFE::TrackProcessor tract_processor (index_image, directions, fixel_TDI, connectivity_matrix, angular_threshold); Thread::run_queue ( loader, Thread::batch (DWI::Tractography::Streamline()), @@ -252,7 +242,7 @@ void run() { // Normalise connectivity matrix and threshold, pre-compute fixel-fixel weights for smoothing. - std::vector > smoothing_weights (num_fixels); + std::vector > smoothing_weights (num_fixels); bool do_smoothing = false; const value_type gaussian_const2 = 2.0 * smooth_std_dev * smooth_std_dev; value_type gaussian_const1 = 1.0; @@ -262,7 +252,7 @@ void run() { } { ProgressBar progress ("normalising and thresholding fixel-fixel connectivity matrix", num_fixels); - for (unsigned int fixel = 0; fixel < num_fixels; ++fixel) { + for (uint32_t fixel = 0; fixel < num_fixels; ++fixel) { auto it = connectivity_matrix[fixel].begin(); while (it != connectivity_matrix[fixel].end()) { const value_type connectivity = it->second.value / value_type (fixel_TDI[fixel]); @@ -275,7 +265,7 @@ void run() { Math::pow2 (positions[fixel][2] - positions[it->first][2])); const Stats::CFE::connectivity smoothing_weight (Stats::CFE::connectivity_value_type(connectivity * gaussian_const1 * std::exp (-std::pow (distance, 2) / gaussian_const2))); if (smoothing_weight.value > connectivity_threshold) - smoothing_weights[fixel].insert (std::pair (it->first, smoothing_weight)); + smoothing_weights[fixel].insert (std::pair (it->first, smoothing_weight)); } // Here we pre-exponentiate each connectivity value by C it->second.value = std::pow (connectivity, cfe_c); @@ -285,14 +275,14 @@ void run() { // Make sure the fixel is fully connected to itself giving it a smoothing weight of 1 Stats::CFE::connectivity self_connectivity; self_connectivity.value = 1.0; - connectivity_matrix[fixel].insert (std::pair (fixel, self_connectivity)); - smoothing_weights[fixel].insert (std::pair (fixel, gaussian_const1)); + connectivity_matrix[fixel].insert (std::pair (fixel, self_connectivity)); + smoothing_weights[fixel].insert (std::pair (fixel, gaussian_const1)); progress++; } } // Normalise smoothing weights - for (size_t fixel = 0; fixel < num_fixels; ++fixel) { + for (uint32_t fixel = 0; fixel < num_fixels; ++fixel) { value_type sum = 0.0; for (auto it = smoothing_weights[fixel].begin(); it != smoothing_weights[fixel].end(); ++it) sum += it->second.value; @@ -302,43 +292,28 @@ void run() { } // Load input data - matrix_type data (num_fixels, filenames.size()); + matrix_type data (num_fixels, identifiers.size()); { - ProgressBar progress ("loading input images", filenames.size()); - for (size_t subject = 0; subject < filenames.size(); subject++) { + ProgressBar progress ("loading input images", identifiers.size()); + for (size_t subject = 0; subject < identifiers.size(); subject++) { LogLevelLatch log_level (0); - Sparse::Image fixel (filenames[subject]); - check_dimensions (fixel, mask_fixel_image, 0, 3); - std::vector temp_fixel_data (num_fixels, 0.0); - - for (auto voxel = Loop(fixel)(fixel, fixel_index_image); voxel; ++voxel) { - fixel_index_image.index(3) = 0; - const int32_t index = fixel_index_image.value(); - fixel_index_image.index(3) = 1; - const int32_t number_fixels = fixel_index_image.value(); - - // for each fixel in the mask, find the corresponding fixel in this subject voxel - for (int32_t i = index; i < index + number_fixels; ++i) { - value_type largest_dp = 0.0; - int index_of_closest_fixel = -1; - for (size_t f = 0; f != fixel.value().size(); ++f) { - const value_type dp = std::abs (directions[i].dot(fixel.value()[f].dir.cast())); - if (dp > largest_dp) { - largest_dp = dp; - index_of_closest_fixel = f; - } - } - if (largest_dp > angular_threshold_dp) - temp_fixel_data[i] = fixel.value()[index_of_closest_fixel].value; - } - } + + auto subject_data = Image::open (Path::join(input_fixel_folder, identifiers[subject])).with_direct_io(); + std::vector subject_data_vector (num_fixels, 0.0); + for (auto i = Loop (index_image)(index_image); i; ++i) { + index_image.index(3) = 1; + uint32_t offset = index_image.value(); + uint32_t fixel_index = 0; + for (auto f = FixelFormat::FixelLoop (index_image) (subject_data); f; ++f, ++fixel_index) + subject_data_vector[offset + fixel_index] = subject_data.value(); + } // Smooth the data for (size_t fixel = 0; fixel < num_fixels; ++fixel) { value_type value = 0.0; - std::map::const_iterator it = smoothing_weights[fixel].begin(); + std::map::const_iterator it = smoothing_weights[fixel].begin(); for (; it != smoothing_weights[fixel].end(); ++it) - value += temp_fixel_data[it->first] * it->second.value; + value += subject_data_vector[it->first] * it->second.value; data (fixel, subject) = value; } progress++; @@ -348,19 +323,30 @@ void run() { if (!data.allFinite()) throw Exception ("input data contains non-finite value(s)"); + + Header output_header (header); + output_header.keyval()["num permutations"] = str(num_perms); + output_header.keyval()["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()["angular threshold"] = str(angular_threshold); + output_header.keyval()["connectivity threshold"] = str(connectivity_threshold); + output_header.keyval()["smoothing FWHM"] = str(smooth_std_dev * 2.3548); + { ProgressBar progress ("outputting beta coefficients, effect size and standard deviation"); auto temp = Math::Stats::GLM::solve_betas (data, design); for (ssize_t i = 0; i < contrast.cols(); ++i) { - write_fixel_output (output_prefix + "beta" + str(i) + ".msf", temp.row(i), input_header, mask_fixel_image, fixel_index_image); + write_fixel_output (Path::join (output_fixel_folder, "beta" + str(i) + ".mif"), temp.row(i), output_header); ++progress; } temp = Math::Stats::GLM::abs_effect_size (data, design, contrast); ++progress; - write_fixel_output (output_prefix + "abs_effect.msf", temp.row(0), input_header, mask_fixel_image, fixel_index_image); ++progress; + write_fixel_output (Path::join (output_fixel_folder, "abs_effect.mif"), temp.row(0), output_header); ++progress; temp = Math::Stats::GLM::std_effect_size (data, design, contrast); ++progress; - write_fixel_output (output_prefix + "std_effect.msf", temp.row(0), input_header, mask_fixel_image, fixel_index_image); ++progress; + write_fixel_output (Path::join (output_fixel_folder, "std_effect.mif"), temp.row(0), output_header); ++progress; temp = Math::Stats::GLM::stdev (data, design); ++progress; - write_fixel_output (output_prefix + "std_dev.msf", temp.row(0), input_header, mask_fixel_image, fixel_index_image); + write_fixel_output (Path::join (output_fixel_folder, "std_dev.mif"), temp.row(0), output_header); } Math::Stats::GLMTTest glm_ttest (data, design, contrast); @@ -368,21 +354,12 @@ void run() { cfe_integrator.reset (new Stats::CFE::Enhancer (connectivity_matrix, cfe_dh, cfe_e, cfe_h)); vector_type empirical_cfe_statistic; - Header output_header (input_header); - output_header.keyval()["num permutations"] = str(num_perms); - output_header.keyval()["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()["angular threshold"] = str(angular_threshold); - output_header.keyval()["connectivity threshold"] = str(connectivity_threshold); - output_header.keyval()["smoothing FWHM"] = str(smooth_std_dev * 2.3548); // If performing non-stationarity adjustment we need to pre-compute the empirical CFE statistic if (do_nonstationary_adjustment) { Stats::PermTest::precompute_empirical_stat (glm_ttest, cfe_integrator, nperms_nonstationary, empirical_cfe_statistic); output_header.keyval()["nonstationary adjustment"] = str(true); - write_fixel_output (output_prefix + "cfe_empirical.msf", empirical_cfe_statistic, output_header, mask_fixel_image, fixel_index_image); + write_fixel_output (Path::join (output_fixel_folder, "cfe_empirical.mif"), empirical_cfe_statistic, output_header); } else { output_header.keyval()["nonstationary adjustment"] = str(false); } @@ -396,10 +373,10 @@ void run() { Stats::PermTest::precompute_default_permutation (glm_ttest, cfe_integrator, empirical_cfe_statistic, cfe_output, cfe_output_neg, tvalue_output); - write_fixel_output (output_prefix + "cfe.msf", cfe_output, output_header, mask_fixel_image, fixel_index_image); - write_fixel_output (output_prefix + "tvalue.msf", tvalue_output, output_header, mask_fixel_image, fixel_index_image); + write_fixel_output (Path::join (output_fixel_folder, "cfe.mif"), cfe_output, output_header); + write_fixel_output (Path::join (output_fixel_folder, "tvalue.mif"), tvalue_output, output_header); if (compute_negative_contrast) - write_fixel_output (output_prefix + "cfe_neg.msf", *cfe_output_neg, output_header, mask_fixel_image, fixel_index_image); + write_fixel_output (Path::join (output_fixel_folder, "cfe_neg.mif"), *cfe_output_neg, output_header); // Perform permutation testing opt = get_options ("notest"); @@ -420,19 +397,19 @@ void run() { uncorrected_pvalues, uncorrected_pvalues_neg); ProgressBar progress ("outputting final results"); - save_matrix (perm_distribution, output_prefix + "perm_dist.txt"); ++progress; + save_matrix (perm_distribution, Path::join (output_fixel_folder, "perm_dist.txt")); ++progress; vector_type pvalue_output (num_fixels); Math::Stats::Permutation::statistic2pvalue (perm_distribution, cfe_output, pvalue_output); ++progress; - write_fixel_output (output_prefix + "fwe_pvalue.msf", pvalue_output, output_header, mask_fixel_image, fixel_index_image); ++progress; - write_fixel_output (output_prefix + "uncorrected_pvalue.msf", uncorrected_pvalues, output_header, mask_fixel_image, fixel_index_image); ++progress; + write_fixel_output (Path::join (output_fixel_folder, "fwe_pvalue.mif"), pvalue_output, output_header); ++progress; + write_fixel_output (Path::join (output_fixel_folder, "uncorrected_pvalue.mif"), uncorrected_pvalues, output_header); ++progress; if (compute_negative_contrast) { - save_matrix (*perm_distribution_neg, output_prefix + "perm_dist_neg.txt"); ++progress; + save_matrix (*perm_distribution_neg, Path::join (output_fixel_folder, "perm_dist_neg.txt")); ++progress; vector_type pvalue_output_neg (num_fixels); Math::Stats::Permutation::statistic2pvalue (*perm_distribution_neg, *cfe_output_neg, pvalue_output_neg); ++progress; - write_fixel_output (output_prefix + "fwe_pvalue_neg.msf", pvalue_output_neg, output_header, mask_fixel_image, fixel_index_image); ++progress; - write_fixel_output (output_prefix + "uncorrected_pvalue_neg.msf", *uncorrected_pvalues_neg, output_header, mask_fixel_image, fixel_index_image); + write_fixel_output (Path::join (output_fixel_folder, "fwe_pvalue_neg.mif"), pvalue_output_neg, output_header); ++progress; + write_fixel_output (Path::join (output_fixel_folder, "uncorrected_pvalue_neg.mif"), *uncorrected_pvalues_neg, output_header); } } } diff --git a/lib/fixel_format/loop.h b/lib/fixel_format/loop.h index 42d7f4b21e..a3476599ea 100644 --- a/lib/fixel_format/loop.h +++ b/lib/fixel_format/loop.h @@ -13,8 +13,8 @@ * */ -#ifndef __sparse_loop_h__ -#define __sparse_loop_h__ +#ifndef __fixelformat_loop_h__ +#define __fixelformat_loop_h__ #include "formats/mrtrix_utils.h" diff --git a/lib/math/stats/permutation.cpp b/lib/math/stats/permutation.cpp index 72a045c62a..97bfe18b7f 100644 --- a/lib/math/stats/permutation.cpp +++ b/lib/math/stats/permutation.cpp @@ -38,7 +38,7 @@ namespace MR bool is_duplicate (const std::vector& perm, - const std::vector >& previous_permutations) + const std::vector >& previous_permutations) { for (size_t p = 0; p < previous_permutations.size(); p++) { if (is_duplicate (perm, previous_permutations[p])) @@ -50,9 +50,9 @@ namespace MR void generate (const size_t num_perms, - const size_t num_subjects, - std::vector >& permutations, - const bool include_default) + const size_t num_subjects, + std::vector >& permutations, + const bool include_default) { permutations.clear(); std::vector default_labelling (num_subjects); diff --git a/lib/math/stats/permutation.h b/lib/math/stats/permutation.h index a2635006d5..e3cfe33004 100644 --- a/lib/math/stats/permutation.h +++ b/lib/math/stats/permutation.h @@ -40,7 +40,7 @@ namespace MR // Note that this function does not take into account grouping of subjects and therefore generated // permutations are not guaranteed to be unique wrt the computed test statistic. - // If the number of subjects is large then the likelihood of generating duplicates is low. + // Providing the number of subjects is large then the likelihood of generating duplicates is low. void generate (const size_t num_perms, const size_t num_subjects, std::vector >& permutations, diff --git a/src/stats/cfe.cpp b/src/stats/cfe.cpp index c91ea4770e..3bf0051da0 100644 --- a/src/stats/cfe.cpp +++ b/src/stats/cfe.cpp @@ -24,10 +24,10 @@ namespace MR - TrackProcessor::TrackProcessor (Image& fixel_indexer, + TrackProcessor::TrackProcessor (Image& fixel_indexer, const std::vector& fixel_directions, std::vector& fixel_TDI, - std::vector >& connectivity_matrix, + std::vector >& connectivity_matrix, const value_type angular_threshold): fixel_indexer (fixel_indexer) , fixel_directions (fixel_directions), @@ -40,18 +40,18 @@ namespace MR bool TrackProcessor::operator () (const SetVoxelDir& in) { // For each voxel tract tangent, assign to a fixel - std::vector tract_fixel_indices; + std::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; - int32_t first_index = fixel_indexer.value(); + uint32_t first_index = fixel_indexer.value(); if (first_index >= 0) { fixel_indexer.index(3) = 1; - int32_t last_index = first_index + fixel_indexer.value(); - int32_t closest_fixel_index = -1; + uint32_t last_index = first_index + fixel_indexer.value(); + uint32_t closest_fixel_index = -1; value_type largest_dp = 0.0; const direction_type dir (i->get_dir().normalized()); - for (int32_t j = first_index; j < last_index; ++j) { + for (uint32_t j = first_index; j < last_index; ++j) { const value_type dp = std::abs (dir.dot (fixel_directions[j])); if (dp > largest_dp) { largest_dp = dp; @@ -86,7 +86,7 @@ namespace MR - Enhancer::Enhancer (const std::vector >& connectivity_map, + Enhancer::Enhancer (const std::vector >& connectivity_map, const value_type dh, const value_type E, const value_type H) : @@ -102,7 +102,7 @@ namespace MR enhanced_stats = vector_type::Zero (stats.size()); value_type max_enhanced_stat = 0.0; for (size_t fixel = 0; fixel < connectivity_map.size(); ++fixel) { - std::map::const_iterator connected_fixel; + std::map::const_iterator connected_fixel; for (value_type h = this->dh; h < stats[fixel]; h += this->dh) { value_type extent = 0.0; for (connected_fixel = connectivity_map[fixel].begin(); connected_fixel != connectivity_map[fixel].end(); ++connected_fixel) diff --git a/src/stats/cfe.h b/src/stats/cfe.h index 37221a8ae9..b6f2319c3f 100644 --- a/src/stats/cfe.h +++ b/src/stats/cfe.h @@ -58,10 +58,10 @@ namespace MR class TrackProcessor { public: - TrackProcessor (Image& fixel_indexer, + TrackProcessor (Image& fixel_indexer, const std::vector& fixel_directions, std::vector& fixel_TDI, - std::vector >& connectivity_matrix, + std::vector >& connectivity_matrix, const value_type angular_threshold); @@ -69,10 +69,10 @@ namespace MR private: - Image fixel_indexer; + Image fixel_indexer; const std::vector& fixel_directions; std::vector& fixel_TDI; - std::vector >& connectivity_matrix; + std::vector >& connectivity_matrix; default_type angular_threshold_dp; }; @@ -81,7 +81,7 @@ namespace MR class Enhancer : public Stats::EnhancerBase { public: - Enhancer (const std::vector >& connectivity_map, + Enhancer (const std::vector >& connectivity_map, const value_type dh, const value_type E, const value_type H); @@ -89,7 +89,7 @@ namespace MR protected: - const std::vector >& connectivity_map; + const std::vector >& connectivity_map; const value_type dh, E, H; }; From b0eafede85ffd41c797897480aa42b54af94e6a0 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 5 Sep 2016 15:26:13 +1000 Subject: [PATCH 098/723] New function DWI::stash_dw_scheme() This function takes the gradient table to be used in a DWI processing application (whether loaded from the image header or the command-line), stores it in a new Header keyval entry 'basis_dw_scheme', and erases the DW scheme from the header (since it will no longer be applicable to the output volume(s)). Closes #375. --- cmd/amp2sh.cpp | 3 +++ cmd/dwi2adc.cpp | 1 + cmd/dwi2fod.cpp | 3 +++ cmd/dwi2noise.cpp | 15 ++++++++------- cmd/dwi2tensor.cpp | 1 + lib/filter/dwi_brain_mask.h | 1 + src/dwi/gradient.h | 20 ++++++++++++++++++++ 7 files changed, 37 insertions(+), 7 deletions(-) diff --git a/cmd/amp2sh.cpp b/cmd/amp2sh.cpp index dbf6c1c6ce..c6f2cea056 100644 --- a/cmd/amp2sh.cpp +++ b/cmd/amp2sh.cpp @@ -229,6 +229,8 @@ void run () 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_valid_DW_scheme (amp); @@ -238,6 +240,7 @@ void run () bzeros = shells.smallest().get_volumes(); dwis = shells.largest().get_volumes(); dirs = DWI::gen_direction_matrix (grad, dwis); + DWI::stash_DW_scheme (header, grad); } } diff --git a/cmd/dwi2adc.cpp b/cmd/dwi2adc.cpp index 6a60c32bcc..07af1929b0 100644 --- a/cmd/dwi2adc.cpp +++ b/cmd/dwi2adc.cpp @@ -101,6 +101,7 @@ void run () { header.datatype() = DataType::Float32; header.ndim() = 4; header.size(3) = 2; + DWI::stash_DW_scheme (header, grad); auto adc = Image::create (argument[1], header); diff --git a/cmd/dwi2fod.cpp b/cmd/dwi2fod.cpp index 3a56f7d4cf..e4dc5e8958 100644 --- a/cmd/dwi2fod.cpp +++ b/cmd/dwi2fod.cpp @@ -252,6 +252,7 @@ void run () shared.init(); header_out.size(3) = shared.nSH(); + DWI::stash_DW_scheme (header_out, shared.grad); auto fod = Image::create (argument[3], header_out); CSD_Processor processor (shared, mask); @@ -283,6 +284,8 @@ void run () shared.init(); + DWI::stash_DW_scheme (header_out, shared.grad); + std::vector< Image > odfs; for (size_t i = 0; i < num_tissues; ++i) { header_out.size (3) = Math::SH::NforL (shared.lmax[i]); diff --git a/cmd/dwi2noise.cpp b/cmd/dwi2noise.cpp index f447859986..493a276dd6 100644 --- a/cmd/dwi2noise.cpp +++ b/cmd/dwi2noise.cpp @@ -69,23 +69,24 @@ void run () WARN ("Command dwi2noise is deprecated. Try using dwidenoise with -noise option instead."); auto dwi_in = Image::open (argument[0]); - - auto header = Header (dwi_in); - header.ndim() = 3; - header.datatype() = DataType::Float32; - auto noise = Image::create (argument[1], header); + const auto grad = DWI::get_valid_DW_scheme (dwi_in); std::vector dwis; Eigen::MatrixXd mapping; { - auto grad = DWI::get_valid_DW_scheme (dwi_in); dwis = DWI::Shells (grad).select_shells (true, true).largest().get_volumes(); - auto dirs = DWI::gen_direction_matrix (grad, dwis); + const auto dirs = DWI::gen_direction_matrix (grad, dwis); mapping = DWI::compute_SH2amp_mapping (dirs); } auto dwi = Adapter::make (dwi_in, 3, container_cast> (dwis)); + auto header = Header (dwi_in); + header.ndim() = 3; + header.datatype() = DataType::Float32; + DWI::stash_DW_scheme (header, grad); + auto noise = Image::create (argument[1], header); + DWI::estimate_noise (dwi, noise, mapping); } diff --git a/cmd/dwi2tensor.cpp b/cmd/dwi2tensor.cpp index da5eab15be..77e9c1fbda 100644 --- a/cmd/dwi2tensor.cpp +++ b/cmd/dwi2tensor.cpp @@ -181,6 +181,7 @@ void run () Header header (dwi); header.datatype() = DataType::Float32; header.ndim() = 4; + DWI::stash_DW_scheme (header, grad); Image* predict = nullptr; opt = get_options ("predicted_signal"); diff --git a/lib/filter/dwi_brain_mask.h b/lib/filter/dwi_brain_mask.h index 90bbad35bd..e65daffc5b 100644 --- a/lib/filter/dwi_brain_mask.h +++ b/lib/filter/dwi_brain_mask.h @@ -72,6 +72,7 @@ namespace MR Header header (input); header.ndim() = 3; + DWI::stash_DW_scheme (header, grad); // Generate a 'master' scratch buffer mask, to which all shells will contribute auto mask_image = Image::scratch (header, "DWI mask"); diff --git a/src/dwi/gradient.h b/src/dwi/gradient.h index 3b06b07a4d..4d17644881 100644 --- a/src/dwi/gradient.h +++ b/src/dwi/gradient.h @@ -162,6 +162,26 @@ namespace MR + //! 'stash' the DW gradient table + /*! Store the _used_ DW gradient table to Header::keyval() key + * 'basis_dw_scheme', and delete the key 'dw_scheme' if it exists. + * This means that the scheme will no longer be identified by function + * parse_DW_scheme(), but still resides within the header data and + * can be extracted manually. This should be used when + * diffusion-weighted images are used to generate something that is + * _not_ diffusion_weighted volumes. + */ + template + void stash_DW_scheme (Header& header, const MatrixType& grad) + { + set_DW_scheme (header, grad); + auto dw_scheme = header.keyval().find ("dw_scheme"); + header.keyval()["basis_dw_scheme"] = dw_scheme->second; + header.keyval().erase (dw_scheme); + } + + + //! get the DW gradient encoding matrix /*! attempts to find the DW gradient encoding matrix, using the following From 722ace91577fc3149be216298ee0c0a34d25a18f Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 6 Sep 2016 02:36:32 +1000 Subject: [PATCH 099/723] dwipreproc: Initial commit of redesign Rather than configuring topup and eddy solely based on user input, the new philosophy of this script is to capture the phase encoding information from the image header(s) (which should have been captured from the initial DICOM import), and call topup / eddy appropriately. The previous functionality of the dwipreproc script is retained (with some small changes to the interface) by providing options that enable the user to manually specify the acquisition protocol design, direction and readout time of EPI phase encoding, in cases where this information is not available in the image header. Additionally, if such options are used but the phase encoding information IS available in the header, the user will be warned if their manual settings differ from those present in the header. Note that this is very much an initial commit of this script, and has not been tested all all, let alone comprehensively. --- docs/reference/scripts/dwipreproc.rst | 39 +- scripts/dwipreproc | 542 ++++++++++++++++---------- scripts/lib/cmdlineParser.py | 9 +- scripts/lib/getPEDir.py | 36 +- 4 files changed, 398 insertions(+), 228 deletions(-) diff --git a/docs/reference/scripts/dwipreproc.rst b/docs/reference/scripts/dwipreproc.rst index 19c01238d7..0f72e1e5dc 100644 --- a/docs/reference/scripts/dwipreproc.rst +++ b/docs/reference/scripts/dwipreproc.rst @@ -8,9 +8,8 @@ Synopsis :: - dwipreproc [ options ] pe_dir input output + dwipreproc [ options ] input output -- *pe_dir*: The phase encode direction; can be a signed axis number (e.g. -0, 1, +2) or a code (e.g. AP, LR, IS) - *input*: The input DWI series to be corrected - *output*: The output corrected image series @@ -22,24 +21,40 @@ Perform diffusion image pre-processing using FSL's eddy tool; including inhomoge Options ------- -Options for passing reversed phase-encode data; one of these options MUST be provided -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Options for specifying the acquisition phase-encoding design; note that one of the -rpe_* options MUST be provided +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- **-rpe_none** Specify explicitly that no reversed phase-encoding image data is provided; eddy will perform eddy current and motion correction only +- **-rpe_none** Specify that no reversed phase-encoding image data is being provided; eddy will perform eddy current and motion correction only -- **-rpe_pair forward reverse** Provide a pair of images to use for inhomogeneity field estimation; note that the FIRST of these two images must have the same phase-encode direction as the input DWIs +- **-rpe_pair** Specify that a set of images will be provided for use in inhomogeneity field estimation (using the -topup_images option), where it is assumed that the FIRST volume(s) of this image has the same phase-encoding direction as the input DWIs, and the LAST volume(s) has precisely the OPPOSITE phase encoding -- **-rpe_all input_revpe** Provide a second DWI series identical to the input series, that has the opposite phase encoding; these will be combined in the output image +- **-rpe_all** Specify that all DWIs have been acquired with opposing phase-encoding, where it is assumed that the second half of the volumes in the input DWIs have corresponding diffusion sensitisation directions to the first half, but were acquired using the opposite phase-encoding direction -Options for the dwipreproc script -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +- **-rpe_header** Specify that the phase-encoding information can be found in the image header(s), and that this is the information that the script should use -- **-cuda** Use the CUDA version of eddy +Other options for the dwipreproc script +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-pe_dir PE** Manually specify the phase encoding direction of the input series; can be a signed axis number (e.g. -0, 1, +2), an axis designator (e.g. RL, PA, IS), or NIfTI axis codes (e.g. i-, j, k) + +- **-readout_time time** Manually specify the total readout time of the input series (in seconds) + +- **-topup_images file** Provide an additional image series that is to be used exclusively by topup for estimating the inhomogeneity field (i.e. it will not form part of the output image series) + +- **-json_import JSON_file** Import image header information from an associated JSON file (may be necessary to determine phase encoding information) + +- **-cuda** Use the CUDA version of eddy (if available) + +Options for importing the diffusion gradient table +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - **-grad** Provide a gradient table in MRtrix format - **-fslgrad bvecs bvals** Provide a gradient table in FSL bvecs/bvals format +Options for exporting the diffusion gradient table +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + - **-export_grad_mrtrix grad** Export the final gradient table in MRtrix format - **-export_grad_fsl bvecs bvals** Export the final gradient table in FSL bvecs/bvals format @@ -72,9 +87,9 @@ References * Smith, S. M.; Jenkinson, M.; Woolrich, M. W.; Beckmann, C. F.; Behrens, T. E.; Johansen-Berg, H.; Bannister, P. R.; De Luca, M.; Drobnjak, I.; Flitney, D. E.; Niazy, R. K.; Saunders, J.; Vickers, J.; Zhang, Y.; De Stefano, N.; Brady, J. M. & Matthews, P. M. Advances in functional and structural MR image analysis and implementation as FSL. NeuroImage, 2004, 23, S208-S219 -* If using -rpe_all option: Skare, S. & Bammer, R. Jacobian weighting of distortion corrected EPI data. Proceedings of the International Society for Magnetic Resonance in Medicine, 2010, 5063 +* If performing recombination of diffusion-weighted volume pairs with opposing phase encoding directions: Skare, S. & Bammer, R. Jacobian weighting of distortion corrected EPI data. Proceedings of the International Society for Magnetic Resonance in Medicine, 2010, 5063 -* If using -rpe_pair or -rpe_all options: Andersson, J. L.; Skare, S. & Ashburner, J. How to correct susceptibility distortions in spin-echo echo-planar images: application to diffusion tensor imaging. NeuroImage, 2003, 20, 870-888 +* If performing EPI susceptibility distortion correction: Andersson, J. L.; Skare, S. & Ashburner, J. How to correct susceptibility distortions in spin-echo echo-planar images: application to diffusion tensor imaging. NeuroImage, 2003, 20, 870-888 -------------- diff --git a/scripts/dwipreproc b/scripts/dwipreproc index e1039b9482..4f3d7e1e6e 100755 --- a/scripts/dwipreproc +++ b/scripts/dwipreproc @@ -2,8 +2,17 @@ # Script for performing DWI pre-processing using FSL 5.0 tools eddy / topup / applytopup -# This script is generally the first operation that will be applied to diffusion image data. The precise details of how this image pre-processing takes place depends heavily on the DWI acquisition; specifically, the presence or absence of reversed phase-encoding data. +# This script is generally the first operation that will be applied to diffusion image data (with the possible exception of dwidenoise). The precise details of how this image pre-processing takes place depends heavily on the DWI acquisition; specifically, the presence or absence of reversed phase-encoding data for the purposes of EPI susceptibility distortion correction. +# The script is capable of handling a wide range of DWI acquisitions with respect to the design of phase encoding directions. This is dependent upon information regarding the phase encoding being embedded within theimage headers. The relevant information should be captured by MRtrix when importing DICOM images; it should also be the case for BIDS-compatible datasets. If the user expects this information to be present within the image headers, the -rpe_header option must be specified. + +# If however such information is not present in the image headers, then it is also possible for the user to manually specify the relevant information regarding phase encoding. This involves the following information: +# * The fundamental acquisition protocol design regarding phase encoding. There are three common acquisition designs that are supported: +# 1. All DWI volumes acquired using the same phase encode parameters, and no additional volumes acquired for the purpose of estimating the inhomogeneity field. In this case, eddy will only perform motion and eddy current distortion correction. This configuration is specified using the -rpe_none option. +# 2. All DWI volumes acquired using the same phase encode parameters; but for the purpose of estimating the inhomogeneity field (and subsequently correcting the resulting distortions in the DWIs), an additional pair (or multiple pairs) of image volumes are acquired, where the first volume(s) has the same phase encoding parameters as the input DWI series, and the second volume(s) has precisely the opposite phase encoding. This configuration is specified using the -rpe_pair option; and the user must additionally provide those images to be used for field estimation using the -topup_images option. +# 3. Every DWI gradient direction is acquired twice: once with one phase encoding configuration, and again using the opposite phase encode direction. The goal here is to combine each pair of images into a single DWI volume per gradient direction, where that recombination takes advantage of the information gained from having two volumes where the signal is distorted in opposite directions in the presence of field inhomogeneity. +# * The (primary) direction of phase encoding. In cases where opposing phase encoding is part of the acquisition protocol (i.e. the reversed phase-encode pair in case 2 above, and all of the DWIs in case 3 above), the -pe_dir option specifies the phase encode direction of the _first_ volume in the relevant volume pair; the second is assumed to be the exact opposite. +# * The total readout time of the EPI acquisition. This affects the magnitude of the image distortion for a given field inhomogeneity. If this information is not provided via the -readout_time option, then a 'sane' default of 0.1s will be assumed. Note that this is not actually expected to influence the estimation of the field; it will result in the field inhomogeneity estimation being scaled by some factor, but as long as it uses the same sane default for the DWIs, the distortion correction should operate as expected. import math, os, sys import lib.app, lib.cmdlineParser @@ -25,25 +34,34 @@ from lib.warnMessage import warnMessage lib.app.author = 'Robert E. Smith (robert.smith@florey.edu.au)' lib.app.addCitation('', 'Andersson, J. L. & Sotiropoulos, S. N. An integrated approach to correction for off-resonance effects and subject movement in diffusion MR imaging. NeuroImage, 2015, 125, 1063-1078', True) lib.app.addCitation('', 'Smith, S. M.; Jenkinson, M.; Woolrich, M. W.; Beckmann, C. F.; Behrens, T. E.; Johansen-Berg, H.; Bannister, P. R.; De Luca, M.; Drobnjak, I.; Flitney, D. E.; Niazy, R. K.; Saunders, J.; Vickers, J.; Zhang, Y.; De Stefano, N.; Brady, J. M. & Matthews, P. M. Advances in functional and structural MR image analysis and implementation as FSL. NeuroImage, 2004, 23, S208-S219', True) -lib.app.addCitation('If using -rpe_all option', 'Skare, S. & Bammer, R. Jacobian weighting of distortion corrected EPI data. Proceedings of the International Society for Magnetic Resonance in Medicine, 2010, 5063', True) -lib.app.addCitation('If using -rpe_pair or -rpe_all options', 'Andersson, J. L.; Skare, S. & Ashburner, J. How to correct susceptibility distortions in spin-echo echo-planar images: application to diffusion tensor imaging. NeuroImage, 2003, 20, 870-888', True) +lib.app.addCitation('If performing recombination of diffusion-weighted volume pairs with opposing phase encoding directions', 'Skare, S. & Bammer, R. Jacobian weighting of distortion corrected EPI data. Proceedings of the International Society for Magnetic Resonance in Medicine, 2010, 5063', True) +lib.app.addCitation('If performing EPI susceptibility distortion correction', 'Andersson, J. L.; Skare, S. & Ashburner, J. How to correct susceptibility distortions in spin-echo echo-planar images: application to diffusion tensor imaging. NeuroImage, 2003, 20, 870-888', True) lib.cmdlineParser.initialise('Perform diffusion image pre-processing using FSL\'s eddy tool; including inhomogeneity distortion correction using FSL\'s topup tool if possible') -lib.app.parser.add_argument('pe_dir', help='The phase encode direction; can be a signed axis number (e.g. -0, 1, +2) or a code (e.g. AP, LR, IS)') lib.app.parser.add_argument('input', help='The input DWI series to be corrected') lib.app.parser.add_argument('output', help='The output corrected image series') -options = lib.app.parser.add_argument_group('Options for the dwipreproc script') -options.add_argument('-cuda', help='Use the CUDA version of eddy', action='store_true', default=False) -options.add_argument('-grad', help='Provide a gradient table in MRtrix format') -options.add_argument('-fslgrad', nargs=2, metavar=('bvecs', 'bvals'), help='Provide a gradient table in FSL bvecs/bvals format') -lib.cmdlineParser.flagMutuallyExclusiveOptions( [ 'grad', 'fslgrad' ] ) -options.add_argument('-export_grad_mrtrix', metavar='grad', help='Export the final gradient table in MRtrix format') -options.add_argument('-export_grad_fsl', nargs=2, metavar=('bvecs', 'bvals'), help='Export the final gradient table in FSL bvecs/bvals format') +grad_export_options = lib.app.parser.add_argument_group('Options for exporting the diffusion gradient table') +grad_export_options.add_argument('-export_grad_mrtrix', metavar='grad', help='Export the final gradient table in MRtrix format') +grad_export_options.add_argument('-export_grad_fsl', nargs=2, metavar=('bvecs', 'bvals'), help='Export the final gradient table in FSL bvecs/bvals format') lib.cmdlineParser.flagMutuallyExclusiveOptions( [ 'export_grad_mrtrix', 'export_grad_fsl' ] ) -rpe_options = lib.app.parser.add_argument_group('Options for passing reversed phase-encode data; one of these options MUST be provided') -rpe_options.add_argument('-rpe_none', action='store_true', help='Specify explicitly that no reversed phase-encoding image data is provided; eddy will perform eddy current and motion correction only') -rpe_options.add_argument('-rpe_pair', nargs=2, metavar=('forward', 'reverse'), help='Provide a pair of images to use for inhomogeneity field estimation; note that the FIRST of these two images must have the same phase-encode direction as the input DWIs') -rpe_options.add_argument('-rpe_all', metavar='input_revpe', help='Provide a second DWI series identical to the input series, that has the opposite phase encoding; these will be combined in the output image') -lib.cmdlineParser.flagMutuallyExclusiveOptions( [ 'rpe_none', 'rpe_pair', 'rpe_all' ], True ) +grad_import_options = lib.app.parser.add_argument_group('Options for importing the diffusion gradient table') +grad_import_options.add_argument('-grad', help='Provide a gradient table in MRtrix format') +grad_import_options.add_argument('-fslgrad', nargs=2, metavar=('bvecs', 'bvals'), help='Provide a gradient table in FSL bvecs/bvals format') +lib.cmdlineParser.flagMutuallyExclusiveOptions( [ 'grad', 'fslgrad' ] ) +options = lib.app.parser.add_argument_group('Other options for the dwipreproc script') +options.add_argument('-pe_dir', metavar=('PE'), help='Manually specify the phase encoding direction of the input series; can be a signed axis number (e.g. -0, 1, +2), an axis designator (e.g. RL, PA, IS), or NIfTI axis codes (e.g. i-, j, k)') +options.add_argument('-readout_time', metavar=('time'), type=float, help='Manually specify the total readout time of the input series (in seconds)') +options.add_argument('-topup_images', metavar=('file'), help='Provide an additional image series that is to be used exclusively by topup for estimating the inhomogeneity field (i.e. it will not form part of the output image series)') +options.add_argument('-json_import', metavar=('JSON_file'), help='Import image header information from an associated JSON file (may be necessary to determine phase encoding information)') +options.add_argument('-cuda', help='Use the CUDA version of eddy (if available)', action='store_true', default=False) +rpe_options = lib.app.parser.add_argument_group('Options for specifying the acquisition phase-encoding design; note that one of the -rpe_* options MUST be provided') +rpe_options.add_argument('-rpe_none', action='store_true', help='Specify that no reversed phase-encoding image data is being provided; eddy will perform eddy current and motion correction only') +rpe_options.add_argument('-rpe_pair', action='store_true', help='Specify that a set of images will be provided for use in inhomogeneity field estimation (using the -topup_images option), where it is assumed that the FIRST volume(s) of this image has the same phase-encoding direction as the input DWIs, and the LAST volume(s) has precisely the OPPOSITE phase encoding') +rpe_options.add_argument('-rpe_all', action='store_true', help='Specify that all DWIs have been acquired with opposing phase-encoding, where it is assumed that the second half of the volumes in the input DWIs have corresponding diffusion sensitisation directions to the first half, but were acquired using the opposite phase-encoding direction') +rpe_options.add_argument('-rpe_header', action='store_true', help='Specify that the phase-encoding information can be found in the image header(s), and that this is the information that the script should use') +lib.cmdlineParser.flagMutuallyExclusiveOptions( [ 'rpe_none', 'rpe_pair', 'rpe_all', 'rpe_header' ], True ) +lib.cmdlineParser.flagMutuallyExclusiveOptions( [ 'rpe_none', 'topup_images' ], False ) # May still technically provide -topup_images even with -rpe_all +lib.cmdlineParser.flagMutuallyExclusiveOptions( [ 'rpe_header', 'pe_dir' ], False ) # Can't manually provide phase-encoding direction if expecting it to be in the header +lib.cmdlineParser.flagMutuallyExclusiveOptions( [ 'rpe_header', 'readout_time' ], False ) # Can't manually provide readout time if expecting it to be in the header lib.app.initialise() @@ -55,10 +73,14 @@ if lib.app.args.rpe_none: PE_design = 'None' elif lib.app.args.rpe_pair: PE_design = 'Pair' + if not lib.app.args.topup_images: + errorMessage('If using the -rpe_pair option, the -topup_images option must be used to provide the image data to be used by topup') elif lib.app.args.rpe_all: PE_design = 'All' +elif lib.app.args.rpe_header: + PE_design = 'Header' else: - errorMessage('Must explicitly specify reversed phase-encoding type (even if none)') + errorMessage('Must explicitly specify phase-encoding acquisition design (even if none)') fsl_path = os.environ.get('FSLDIR', '') if not fsl_path: @@ -83,11 +105,10 @@ if not PE_design == 'None': eddy_cmd = getFSLEddyPath(lib.app.args.cuda) fsl_suffix = getFSLSuffix() -PE_dir = getPEDir(lib.app.args.pe_dir) - lib.app.checkOutputFile(lib.app.args.output) + # Convert all input images into MRtrix format and store in temprary directory first; # that way getHeaderInfo() can be run multiple times without having to repeatedly parse e.g. DICOM data lib.app.makeTempDir() @@ -97,238 +118,361 @@ if lib.app.args.grad: grad_option = ' -grad ' + getUserPath(lib.app.args.grad, True) elif lib.app.args.fslgrad: grad_option = ' -fslgrad ' + getUserPath(lib.app.args.fslgrad[0], True) + ' ' + getUserPath(lib.app.args.fslgrad[1], True) -runCommand('mrconvert ' + getUserPath(lib.app.args.input, True) + ' ' + os.path.join(lib.app.tempDir, 'series.mif') + grad_option) -if PE_design == 'Pair': - runCommand('mrconvert ' + getUserPath(lib.app.args.rpe_pair[0], True) + ' ' + os.path.join(lib.app.tempDir, 'pair1.mif')) - runCommand('mrconvert ' + getUserPath(lib.app.args.rpe_pair[1], True) + ' ' + os.path.join(lib.app.tempDir, 'pair2.mif')) -elif PE_design == 'All': - runCommand('mrconvert ' + getUserPath(lib.app.args.rpe_all, True) + ' ' + os.path.join(lib.app.tempDir, 'series2.mif')) +json_option = '' +if lib.app.args.json_import: + json_option = ' -json_import ' + getUserPath(lib.app.args.json_import, True) +runCommand('mrconvert ' + getUserPath(lib.app.args.input, True) + ' ' + os.path.join(lib.app.tempDir, 'dwi.mif') + grad_option + json_option) +if lib.app.args.topup_images: + runCommand('mrconvert ' + getUserPath(lib.app.args.topup_images, True) + ' ' + os.path.join(lib.app.tempDir, 'topup_in.mif')) lib.app.gotoTempDir() + # Get information on the input images, particularly so that their validity can be checked -series_size = getHeaderInfo('series.mif', 'size').split() -grad = getHeaderInfo('series.mif', 'dwgrad').split('\n') -stride = getHeaderInfo('series.mif', 'stride') -if PE_design == 'Pair': - Pair1_size = getHeaderInfo('pair1.mif', 'size').split() - Pair2_size = getHeaderInfo('pair2.mif', 'size').split() -elif PE_design == 'All': - series2_size = getHeaderInfo('series2.mif', 'size').split() +dwi_size = [ int(s) for s in getHeaderInfo('dwi.mif', 'size').split() ] +dwi_pe_scheme = [ line.split() for line in getHeaderProperty('dwi.mif', 'pe_scheme').split('\n') ] +if lib.app.args.topup_images: + topup_size = [ int[s] for s in getHeaderInfo('topup_in.mif', 'size').split() ] + if not len(topup_size) == 4: + errorMessage('File provided using -topup_images option must contain more than one image volume') + topup_pe_scheme = [ line.split() for line in getHeaderProperty('topup_in.mif', 'pe_scheme').split('\n') ] +grad = [ float(f) for f in (line.split() for line in getHeaderInfo('dwi.mif', 'dwgrad').split('\n')) ] +stride = getHeaderInfo('dwi.mif', 'stride') num_volumes = 1 -if len(series_size) == 4: - num_volumes = int(series_size[3]) +if len(dwi_size) == 4: + num_volumes = dwi_size[3] + -# Perform necessary checks on input images +# Since we want to access user-defined phase encoding information regardless of whether or not +# such information is present in the header, let's grab it here +manual_pe_dir = None +if lib.app.args.pe_dir: + manual_pe_dir = getPEDir(lib.app.args.pe_dir) +manual_trt = None +if lib.app.args.readout_time: + manual_trt = float(lib.app.args.readout_time) + + + +# Perform initial checks on input images if not grad: errorMessage('No diffusion gradient table found') if not len(grad) == num_volumes: errorMessage('Number of volumes in gradient table does not match input image') -if PE_design == 'Pair': - - # Check the number of b=0 images - Pair1_bzero_count = 1 - if len(Pair1_size) == 4: - Pair1_bzero_count = int(Pair1_size[3]) - elif len(Pair1_size) != 3: - errorMessage ('First image of reversed phase-encode pair must be a 3D or 4D image') - Pair2_bzero_count = 1 - if len(Pair2_size) == 4: - Pair2_bzero_count = int(Pair2_size[3]) - elif len(Pair2_size) != 3: - errorMessage ('Second image of reversed phase-encode pair must be a 3D or 4D image') - - # Do other verifications on inputs - if Pair1_size[:3] != Pair2_size[:3]: - errorMessage ('Dimensions of reversed phase-encode image pair do not match') - if Pair1_bzero_count != Pair2_bzero_count: - warnMessage ('Inequal number of b=0 volumes in pair; registration may be biased') - if series_size[:3] != Pair1_size[:3]: - errorMessage ('Spatial dimensions of reversed phase-encode pair does not match DWIs') - -elif PE_design == 'All': - - if series2_size != series_size: - errorMessage ('Dimensions of input images do not match') - - -# If no axes need to be cropped, use the original 4D volume with the image pair -# Otherwise, need to call mrcrop with the appropriate options, and pass the modified images to topup -series_path = 'series.mif' -if not PE_design == 'None': - pair1_path = 'pair1.mif' - pair2_path = 'pair2.mif' - series2_path = 'series2.mif' - # For any non-even axis sizes, crop the first voxel along that dimension - crop_option = '' - for axis, axis_size in enumerate(series_size[:3]): - if int(axis_size)%2: - crop_option += ' -axis ' + str(axis) + ' 1 ' + str(int(axis_size)-1) +do_topup = not PE_scheme == 'None' - if crop_option: - warnMessage('Input images contain at least one non-even dimension; cropping images for topup / eddy compatibility') - runCommand('mrcrop series.mif series_crop.mif' + crop_option) - delFile('series.mif') - series_path = 'series_crop.mif' - if PE_design == 'Pair': - runCommand('mrcrop pair1.mif pair1_crop.mif' + crop_option) - delFile('pair1.mif') - pair1_path = 'pair1_crop.mif' - runCommand('mrcrop pair2.mif pair2_crop.mif' + crop_option) - delFile('pair2.mif') - pair2_path = 'pair2_crop.mif' - else: # 'All' - runCommand('mrcrop series2.mif series2_crop.mif' + crop_option) - delFile('series2.mif') - series2_path = 'series2_crop.mif' - - -# Convert the input files as necessary for FSL tools -if PE_design == 'None': - runCommand('mrconvert ' + series_path + ' dwi_pre_topup.nii -stride -1,+2,+3,+4') -if PE_design == 'Pair': - runCommand('mrconvert ' + series_path + ' dwi_pre_topup.nii -stride -1,+2,+3,+4') - runCommand('mrcat ' + pair1_path + ' ' + pair2_path + ' - -axis 3 | mrconvert - topup_in.nii -stride -1,+2,+3,+4') -elif PE_design == 'All': - runCommand('dwiextract ' + series_path + ' -bzero pair1.mif') - runCommand('dwiextract ' + series2_path + ' -bzero pair2.mif') - runCommand('mrcat pair1.mif pair2.mif - -axis 3 | mrconvert - topup_in.nii -stride -1,+2,+3,+4') - runCommand('mrconvert ' + series_path + ' dwi1_pre_topup.nii -stride -1,+2,+3,+4') - delFile(series_path) - runCommand('mrconvert ' + series2_path + ' dwi2_pre_topup.nii -stride -1,+2,+3,+4') - delFile(series2_path) - runCommand('mrcat dwi1_pre_topup.nii dwi2_pre_topup.nii dwi_pre_eddy.nii -axis 3') - - -# Calculate how many b=0 volumes are present in each phase-encode direction -rpe_b0_count = [ 1, 0 ] # Always contains integers, _not_ strings -if PE_design == 'Pair' or PE_design == 'All': - rpe_b0_count[1] = 1 - temp = getHeaderInfo(pair1_path, 'size').split() - if len(temp) == 4: - rpe_b0_count[0] = int(temp[3]) - temp = getHeaderInfo(pair2_path, 'size').split() - if len(temp) == 4: - rpe_b0_count[1] = int(temp[3]) - - -# Construct a topup/eddy configuration file -printMessage('Creating phase-encoding configuration file') -config_file = open('config.txt', 'w') -config_line = [ '0', '0', '0', '0.1' ] -if PE_dir[1]: - config_line[PE_dir[0]] = '-1' -else: - config_line[PE_dir[0]] = '1' -for lines in range(0, rpe_b0_count[0]): - config_file.write(' '.join (config_line) + '\n') -if PE_dir[1]: - config_line[PE_dir[0]] = '1' + + +dwi_manual_pe_scheme = None +topup_manual_pe_scheme = None +auto_trt = 0.1 +auto_trt_warning = False +if manual_pe_dir: + + if manual_trt: + trt = manual_trt + else: + trt = auto_trt + auto_trt_warning = True + + # Still construct the manual PE scheme even with 'None' or 'Pair': + # there may be information in the header that we need to compare against + if PE_scheme == 'None': + line = manual_pe_dir.extend(trt) + dwi_manual_pe_scheme = ' '.join(str(i) for i in line) * num_volumes + + # With 'Pair', also need to construct the manual scheme for topup_images + elif PE_scheme == 'Pair': + line = manual_pe_dir.extend(trt) + dwi_manual_pe_scheme = ' '.join(str(i) for i in line) * num_volumes + num_topup_volumes = topup_size[3] + # Assume that first half of volumes have same direction as series; + # second half have the opposite direction + topup_manual_pe_scheme = ' '.join(str(i) for i in line) * (num_topup_volumes/2) + line = [ -i for i in manual_pe_dir].extend(trt) + topup_manual_pe_scheme.append(' '.join(str(i) for i in line) * (num_topup_volumes/2)) + + # If -rpe_all, need to scan through grad and figure out the pairings + # This will be required if relying on user-specified phase encode direction + # It will also be required at the end of the script for the manual recombination + elif PE_scheme == 'All': + grad_matchings = [ num_volumes for row in grad ] + grad_pairs = [ ] + for index1 in enumerate(grad): + if grad_matchings[index1] == num_volumes: # As yet unpaired + for index2 in range(index1+1, num_volumes): + if grad[index1] == grad[index2]: + grad_matchings[index1] = index2; + grad_matchings[index2] = index1; + grad_pairs.append([index1, index2]) + if not len(grad_pairs) == num_volumes/2: + errorMessage('Unable to determine matching DWI volume pairs for reversed phase-encode combination') + # Construct manual PE scheme here: + # Regardless of whether or not there's a scheme in the header, need to have it: + # if there's one in the header, want to compare to the manually-generated one + dwi_manual_pe_scheme = [ ] + for index in range(0, num_volumes): + if grad_matchings[index] > index: + dwi_manual_pe_scheme.append(manual_pe_dir.extend(trt)) + else: + dwi_manual_pe_scheme.append( [ -i for i in manual_pe_dir ].extend(trt)) + + + +def scheme_dirs_match(one, two): + for line_one, line_two in zip(one, two): + if not line_one[0:3] == line_two[0:3]: + return False + return True + +def scheme_times_match(one, two): + for line_one, line_two in zip(one, two): + if abs(header_line[3] - manual_line[3]) > 5e-3: + return False + return True + + + +if dwi_pe_scheme: + overwrite_scheme = False + if manual_pe_dir: + # Compare manual specification to that read from the header; + # overwrite & give warning to user if they differ + # Bear in mind that this could even be the case for -rpe_all; + # relying on earlier code having successfully generated the 'appropriate' + # PE scheme for the input volume based on the diffusion gradient table + if not scheme_dirs_match(dwi_pe_scheme, dwi_manual_pe_scheme): + warnMessage('User-defined phase-encoding direction design does not match what is stored in DWI image header; proceeding with user specification') + overwrite_scheme = True + if manual_trt: + # Compare manual specification to that read from the header + if not scheme_times_match(dwi_pe_scheme, dwi_manual_pe_scheme): + warnMessage('User-defined total readout time does not match what is stored in DWI image header; proceeding with user specification') + overwrite_scheme = True + if overwrite_scheme: + dwi_pe_scheme = dwi_pe_manual_scheme # May be used later for triggering volume recombination + else: + dwi_manual_pe_scheme = None # So that the scheme stored within the header will be used else: - config_line[PE_dir[0]] = '-1' -for lines in range(0, rpe_b0_count[1]): - config_file.write(' '.join (config_line) + '\n') -config_file.close() + # Nothing in the header; rely entirely on user specification + if PE_design == 'Header': + errorMessage('No phase encoding information found in DWI image header') + if not manual_pe_dir: + errorMessage('No phase encoding information provided either in header or at command-line') + if auto_trt_warning: + warnMessage('Total readout time not provided at command-line; assuming sane default of ' + str(auto_trt)) -# Construct an index file for eddy -indices = [ ] -if PE_design == 'None' or PE_design == 'Pair': - for volume in range(0, num_volumes): - indices.append('1') -else: # 'All' - for volume in range(0, num_volumes): - indices.append('1') - for volume in range(0, num_volumes): - indices.append(str(rpe_b0_count[0]+1)) -with open('indices.txt', 'w') as index_file: - index_file.write(' '.join(indices)); +# Deal with the phase-encoding of the images to be fed to topup (if applicable) +if lib.app.args.topup_images: + + # 3 possible sources of PE information: DWI header, topup image header, command-line + # Any pair of these may conflict, and any one could be absent + auto_trt_warning = False + overwrite_scheme = False + + # Have to switch here based on phase-encoding acquisition design + if PE_design == 'Pair': + num_topup_volumes = topup_size[3] + if num_topup_volumes%2: + errorMessage('If using -rpe_pair option, image provided using -topup_images must contain an even number of volumes'); + # Criteria: + # * If present in own header, ignore DWI header entirely - + # - If also provided at command-line, look for conflict & report + # - If not provided at command-line, nothing to do + # * If _not_ present in own header: + # - If provided at command-line, infer appropriately + # - If not provided at command-line, but the DWI header has that information, infer appropriately + if topup_pe_scheme: + if manual_pe_dir: + if not scheme_dirs_match(topup_pe_scheme, topup_manual_pe_scheme): + warnMessage('User-defined phase-encoding direction design does not match what is stored in topup image header; proceeding with user specification') + overwrite_scheme = True + if manual_trt: + if not scheme_times_match(topup_pe_scheme, topup_manual_pe_scheme): + warnMessage('User-defined total readout time does not match what is stored in topup image header; proceeding with user specification') + overwrite_scheme = True + if not overwrite_scheme: + topup_manual_pe_scheme = None # So that the scheme stored within the hedaer will be used + else: + if manual_trt: + trt = manual_trt + else: + trt = auto_trt + if manual_pe_dir: + topup_pe_scheme = topup_manual_pe_scheme + if not manual_trt: + auto_trt_warning = True + else: + if not series_pe_scheme: + errorMessage('Unable to determine phase-encoding design of topup images') + line = series_pe_scheme[0] + topup_manual_pe_scheme = ' '.join(str(i) for i in line) * (num_topup_volumes/2) + line = [ -i for i in line[0:3]].extend(trt) + topup_manual_pe_scheme.append(' '.join(str(i) for i in line) * (num_topup_volumes/2)) + + elif PE_design == 'All': + # Criteria: + # * If present in own header: + # - Nothing to do + # * If _not_ present in own header: + # - Don't have enough information to proceed + # - Is this too harsh? (e.g. Have rules by which it may be inferred from the DWI header / command-line) + if not topup_pe_scheme: + errorMessage('If explicitly including topup images when using -rpe_all option, they must come with their own associated phase-encoding information') + + elif PE_design == 'Header': + # Criteria: + # * If present in own header: + # Nothing to do (-pe_dir option is mutually exclusive) + # * If _not_ present in own header: + # Cannot proceed + if not topup_pe_scheme: + errorMessage('No phase-encoding information present in topup image header'); + +else: # No topup images explicitly provided: In some cases, can extract appropriate b=0 images from DWI + + # If using 'All' or 'Header', and haven't been given any topup images, need to extract the b=0 volumes from the series, + # preserving phase-encoding information while doing so + # Preferably also make sure that there's some phase-encoding contrast in there... + # With -rpe_all, need to write inferred phase-encoding to file and import before using dwiextract so that the phase-encoding + # of the extracted b=0's is propagated to the generated b=0 series + if PE_design == 'All': + with open('series_manual_pe_scheme.txt', 'w') as f: + for line in series_manual_pe_scheme: + f.write(' '.join(str(i) for i in line)) + runCommand('mrconvert series.mif -import_pe_table series_manual_pe_scheme.txt - | dwiextract - topup_in.mif -bzero') + elif PE_design == 'Header': + runCommand('dwiextract dwi.mif -bzero topup_in.mif') + else: + errorMessage('Script error: Shouldn\'t be here') + # If there's no contrast remaining in the phase-encoding scheme, it'll be written to + # PhaseEncodingDirection and TotalReadoutTime rather than pe_scheme + if not getHeaderProperty('topup_in.mif', 'pe_scheme'): + runCommand('mrconvert bzeros.mif topup_in.nii -stride -1,+2,+3,+4 -export_pe_table topup_datain.txt') + delFile('topup_in.mif') + -eddy_in_topup = '' +# Need gradient table if running dwi2mask after applytopup to derive a brain mask for eddy +runCommand('mrinfo dwi.mif -export_grad_mrtrix grad.b') -if PE_design == 'None': - # Generate a processing mask for eddy based on the input series - runCommand('dwi2mask ' + series_path + ' - | maskfilter - dilate - | mrconvert - mask.nii -datatype float32 -stride -1,+2,+3') - runCommand('mrconvert ' + series_path + ' - -stride -1,+2,+3,+4 | mrinfo - -export_grad_fsl bvecs bvals') -else: +eddy_in_topup_option = '' +if do_topup: - # Perform the field estimation - runCommand(topup_cmd + ' --imain=topup_in.nii --datain=config.txt --out=field --fout=field_map' + fsl_suffix + ' --config=' + topup_config_path) + # If no axes need to be cropped, use the original topup input volumes + # Otherwise, need to call mrcrop with the appropriate options, and pass those to topup + topup_in_path = 'topup_in.mif' + # For any non-even axis sizes, crop the first voxel along that dimension + # TODO This primarily applies to topup - don't recall if eddy bugs out or not + crop_option = '' + for axis, axis_size in enumerate(topup_size[:3]): + if int(axis_size)%2: + crop_option += ' -axis ' + str(axis) + ' 1 ' + str(int(axis_size)-1) + if crop_option: + warnMessage('Topup images contain at least one non-even dimension; cropping images for topup compatibility') + runCommand('mrcrop topup_in.mif topup_in_crop.mif' + crop_option) + delFile('topup_in.mif') + topup_in_path = 'topup_in_crop.mif' + # Do the conversion in preparation for topup + runCommand('mrconvert ' + topup_in_path + ' topup_in.nii -stride -1,+2,+3,+4 -export_pe_table topup_datain.txt') + + # Run topup + runCommand(topup_cmd + ' --imain=topup_in.nii --datain=topup_datain.txt --out=field --fout=field_map' + fsl_suffix + ' --config=' + topup_config_path) # Apply the warp field to the input image series to get an initial corrected volume estimate - if PE_design == 'Pair': - runCommand(applytopup_cmd + ' --imain=dwi_pre_topup.nii --datain=config.txt --inindex=1 --topup=field --out=dwi_post_topup' + fsl_suffix + ' --method=jac') - else: # All - runCommand(applytopup_cmd + ' --imain=dwi1_pre_topup.nii --datain=config.txt --inindex=1 --topup=field --out=dwi1_post_topup' + fsl_suffix + ' --method=jac --interp=trilinear') - delFile('dwi1_pre_topup.nii') - runCommand(applytopup_cmd + ' --imain=dwi2_pre_topup.nii --datain=config.txt --inindex=' + str(rpe_b0_count[0]+1) + ' --topup=field --out=dwi2_post_topup' + fsl_suffix + ' --method=jac --interp=trilinear') - delFile('dwi2_pre_topup.nii') - runCommand('mrcat dwi1_post_topup' + fsl_suffix + ' dwi2_post_topup' + fsl_suffix + ' dwi_post_topup' + fsl_suffix + ' -axis 3') - delFile('dwi1_post_topup' + fsl_suffix) - delFile('dwi2_post_topup' + fsl_suffix) - - - # Create the diffusion encoding table in bvecs / bvals format; - # if 'All', need to manually perform the combination - # Create the diffusion gradient table in FSL format - # Make sure the strides are identical to the image actually being passed to eddy before exporting the gradient table - if PE_design == 'Pair': - runCommand('mrconvert ' + series_path + ' - -stride -1,+2,+3,+4 | mrinfo - -export_grad_fsl bvecs bvals') + # applytopup can't receive the complete DWI input and correct it as a whole, because the phase-encoding + # details may vary between volumes + import_dwi_pe_table_option = '' + if dwi_manual_pe_scheme: + with open('dwi_manual_pe_scheme.txt', 'w') as f: + for line in dwi_manual_pe_scheme: + f.write(' '.join(line)) + import_dwi_pe_table_option = ' -import_pe_table dwi_manual_pe_scheme.txt' + runCommand('mrconvert dwi.mif' + import_dwi_pe_table_option + ' - | mrinfo - -export_pe_eddy applytopup_config.txt applytopup_indices.txt') else: - # Concatenate the diffusion gradient table twice - with open('grad.b', 'w') as outfile: - for line in grad: - outfile.write (line + '\n') - for line in grad: - outfile.write (line + '\n') - runCommand('mrconvert dwi_post_topup' + fsl_suffix + ' - -grad grad.b | mrinfo - -export_grad_fsl bvecs bvals') + runCommand('mrinfo ' + topup_in_path + ' -export_pe_eddy applytopup_config.txt applytopup_indices.txt') + + applytopup_index_list = [ ] + applytopup_image_list = [ ] + index = 1 + with open('applytopup_config.txt', 'r') as f: + for line in f: + applytopup_index_list.append (index) + image_path = 'dwi_pe_' + str(index) + '.nii' + runCommand('dwiextract dwi.mif' + import_dwi_pe_table_option + ' -pe ' + ','.join(line.split()) + ' ' + image_path) + index += 1 + + # Finally ready to run applytopup + runCommand(applytopup_cmd + ' --imain=' + ','.join(applytopup_image_list) + ' --datain=applytopup_config.txt --inindex=' + ','.join(applytopup_index_list) + ' --topup=field --out=dwi_post_topup' + fsl_suffix + ' --method=jac') + + # Use the initial corrected volumes to derive a brain mask for eddy + runCommand('mrconvert dwi_post_topup' + fsl_suffix + ' -grad grad.b - | dwi2mask - - | maskfilter - dilate - | mrconvert - mask.nii -datatype float32 -stride -1,+2,+3') + eddy_in_topup_option = ' --topup=field' - # Use the initial corrected image series from applytopup to derive a processing mask for eddy - runCommand('mrconvert dwi_post_topup' + fsl_suffix + ' -fslgrad bvecs bvals - | dwi2mask - - | maskfilter - dilate - | mrconvert - mask.nii -datatype float32 -stride -1,+2,+3') +else: + + # Generate a processing mask for eddy based on the uncorrected input DWIs + runCommand('dwi2mask dwi.mif - | maskfilter - dilate - | mrconvert - mask.nii -datatype float32 -stride -1,+2,+3') - eddy_in_topup = ' --topup=field' # Run eddy -if PE_design == 'None' or PE_design == 'Pair': - eddy_in = 'dwi_pre_topup.nii' -else: # 'All' - eddy_in = 'dwi_pre_eddy.nii' -runCommand(eddy_cmd + ' --imain=' + eddy_in + ' --mask=mask.nii --index=indices.txt --acqp=config.txt --bvecs=bvecs --bvals=bvals' + eddy_in_topup + ' --out=dwi_post_eddy') +runCommand('mrconvert dwi.mif' + import_dwi_pe_table_option + ' dwi.nii -stride -1,+2,+3,+4 -export_grad_fsl bvecs bvals -export_pe_eddy eddy_config.txt eddy_indices.txt') +delFile('dwi.mif') +runCommand(eddy_cmd + ' --imain=dwi.nii --mask=mask.nii --acqp=eddy_config.txt --index=eddy_indices.txt --bvecs=bvecs --bvals=bvals' + eddy_in_topup_option + ' --out=dwi_post_eddy') + # Get the axis strides from the input series, so the output image can be modified to match stride_option = ' -stride ' + stride.replace(' ', ',') + # Check to see whether or not eddy has provided a rotated bvecs file; # if it has, import this into the output image bvecs_path = 'dwi_post_eddy.eddy_rotated_bvecs' if not os.path.isfile(bvecs_path): - warnMessage('eddy has not provided rotated bvecs file; using original gradient table') + warnMessage('eddy has not provided rotated bvecs file; using original gradient table. Recommend updating FSL eddy to version 5.0.9 or later.') bvecs_path = 'bvecs' -if PE_design == 'None' or PE_design == 'Pair': + +# Determine whether or not volume recombination should be performed +# This could be either due to use of -rpe_all option, or just due to the data provided with -rpe_header +# Rather than trying to re-use the code that was used in the case of -rpe_all, run fresh code +# The phase-encoding scheme needs to be checked also +volume_matchings = [ num_volumes for row in grad ] +volume_pairs = [ ] +for index1 in enumerate(grad): + if volume_matchings[index1] == num_volumes: # As yet unpaired + for index2 in range(index1+1, num_volumes): + # Here, need to check both gradient matching and reversed phase-encode direction + if not max(abs(dwi_pe_scheme[index1][0:3] + dwi_pe_scheme[index2][0:3])) and grad[index1] == grad[index2]: + volume_matchings[index1] = index2; + volume_matchings[index2] = index1; + volume_pairs.append([index1, index2]) + +if not len(volume_pairs) == num_volumes/2: # Convert the resulting volume to the output image, and re-insert the diffusion encoding runCommand('mrconvert dwi_post_eddy' + fsl_suffix + ' result.mif' + stride_option + ' -fslgrad ' + bvecs_path + ' bvals') - -else: # 'All' + +else: + printMessage('Detected matching DWI volumes with opposing phase encoding; performing explicit volume recombination') # Perform a manual combination of the volumes output by eddy, since LSR is disabled - + # Generate appropriate bvecs / bvals files # Particularly if eddy has provided rotated bvecs, since we're combining two volumes into one that # potentially have subject rotation between them (and therefore the sensitisation direction is @@ -365,6 +509,7 @@ else: # 'All' f.write(' '.join(bvals)) + # Prior to 5.0.8, a bug resulted in the output field map image from topup having an identity transform, # regardless of the transform of the input image # Detect this, and manually replace the transform if necessary @@ -382,6 +527,7 @@ else: # 'All' field_map_image = 'field_map_fix' + fsl_suffix + # Derive the weight images # Scaling term for field map is identical to the bandwidth provided in the topup config file # (converts Hz to pixel count; that way a simple image gradient can be used to get the Jacobians) @@ -398,6 +544,7 @@ else: # 'All' runCommand('mrcalc weight1.mif weight2.mif -add sum_weights.mif') + # Manually combine corresponding volumes from EDDY output runCommand('mrconvert dwi_post_eddy' + fsl_suffix + ' corrected1.mif -coord 3 0:' + str(num_volumes-1)) runCommand('mrconvert dwi_post_eddy' + fsl_suffix + ' corrected2.mif -coord 3 ' + str(num_volumes) + ':' + str((num_volumes*2)-1)) @@ -405,6 +552,7 @@ else: # 'All' runCommand('mrcalc corrected1.mif weight1.mif -mult corrected2.mif weight2.mif -mult -add sum_weights.mif -divide 0.0 -max - | mrconvert - result.mif -fslgrad bvecs_combined bvals_combined' + stride_option) + # Finish! # Also export the gradient table to the path requested by the user if necessary grad_export_option = '' diff --git a/scripts/lib/cmdlineParser.py b/scripts/lib/cmdlineParser.py index 1569c7a340..4116fc27a7 100644 --- a/scripts/lib/cmdlineParser.py +++ b/scripts/lib/cmdlineParser.py @@ -86,10 +86,13 @@ def parse_args(self): count += 1 break if count > 1: - sys.stderr.write('\nError: You cannot use more than one of the following options: ' + ', '.join([ '-' + o for o in group[0] ]) + '\n\n') - sys.exit(2) + sys.stderr.write('\nError: You cannot use more than one of the following options: ' + ', '.join([ '-' + o for o in group[0] ]) + '\n') + sys.stderr.write('(Consult the help page for more information: ' + self.prog + ' -help)\n\n') + sys.exit(1) if group[1] and not count: - sys.stderr.write('\nError: One of the following options must be provided: ' + ', '.join([ '-' + o for o in group[0] ]) + '\n\n') + sys.stderr.write('\nError: One of the following options must be provided: ' + ', '.join([ '-' + o for o in group[0] ]) + '\n') + sys.stderr.write('(Consult the help page for more information: ' + self.prog + ' -help)\n\n') + sys.exit(1) return args diff --git a/scripts/lib/getPEDir.py b/scripts/lib/getPEDir.py index 3736a36a9a..198c084149 100644 --- a/scripts/lib/getPEDir.py +++ b/scripts/lib/getPEDir.py @@ -5,36 +5,40 @@ def getPEDir(string): try: PE_axis = abs(int(string)) if PE_axis > 2: - errorMessage('Phase encode axis must be either 0, 1 or 2') - reverse = (string[0] == '-') # Allow -0 - pe_dir = ( PE_axis, reverse ) + errorMessage('When specified as a number, phase encode axis must be either 0, 1 or 2') + reverse = (string.contains('-')) # Allow -0 + pe_dir = [0,0,0] + if reverse: + pe_dir[PE_axis] = -1 + else: + pe_dir[PE_axis] = 1 except: string = string.lower() if string == 'lr': - pe_dir = ( 0, False ) + pe_dir = [1,0,0] elif string == 'rl': - pe_dir = ( 0, True ) + pe_dir = [-1,0,0] elif string == 'pa': - pe_dir = ( 1, False ) + pe_dir = [0,1,0] elif string == 'ap': - pe_dir = ( 1, True ) + pe_dir = [0,-1,0] elif string == 'is': - pe_dir = ( 2, False ) + pe_dir = [0,0,1] elif string == 'si': - pe_dir = ( 2, True ) + pe_dir = [0,0,-1] elif string == 'i': - pe_dir = ( 0, False ) + pe_dir = [1,0,0] elif string == 'i-': - pe_dir = ( 0, True ) + pe_dir = [-1,0,0] elif string == 'j': - pe_dir = ( 1, False ) + pe_dir = [0,1,0] elif string == 'j-': - pe_dir = ( 1, True ) + pe_dir = [0,-1,0] elif string == 'k': - pe_dir = ( 2, False ) + pe_dir = [0,0,1] elif string == 'k-': - pe_dir = ( 2, True ) + pe_dir = [0,0,-1] else: errorMessage('Unrecognized phase encode direction specifier: ' + string) - debugMessage(string + ' -> ' + str(pe_dir) + ' (axis, reversed)') + debugMessage(string + ' -> ' + str(pe_dir)) return pe_dir From b9d35fd109933629f10b730817c74c2c3c1d5554 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 6 Sep 2016 18:17:03 +1000 Subject: [PATCH 100/723] dwipreproc: Further development following rewrite - mrinfo: New option -petable to print the phase encoding table to stdout. - Use lowercase keys for phase encoding related entries, since they may be converted to lowercase elsewhere in processing. - Add some missing dependencies in Python script library functions. - Rename script function getTempImagePath() to getTempFilePath(). --- cmd/mrinfo.cpp | 38 +++--- docs/reference/commands/mrinfo.rst | 2 + lib/file/dicom/mapper.cpp | 2 +- lib/file/json_utils.cpp | 3 +- lib/phase_encoding.cpp | 5 +- lib/phase_encoding.h | 20 +-- scripts/dwipreproc | 123 ++++++++++-------- scripts/lib/binaryInPath.py | 1 + scripts/lib/getFSLEddyPath.py | 1 + scripts/lib/getFSLSuffix.py | 3 +- scripts/lib/getPEScheme.py | 20 +++ ...getTempImagePath.py => getTempFilePath.py} | 12 +- scripts/lib/runCommand.py | 1 + 13 files changed, 143 insertions(+), 88 deletions(-) create mode 100644 scripts/lib/getPEScheme.py rename scripts/lib/{getTempImagePath.py => getTempFilePath.py} (81%) diff --git a/cmd/mrinfo.cpp b/cmd/mrinfo.cpp index ef44f08b32..7e1ee18a38 100644 --- a/cmd/mrinfo.cpp +++ b/cmd/mrinfo.cpp @@ -94,7 +94,8 @@ void usage () + Option ("shells", "list the average b-value of each shell") + Option ("shellcounts", "list the number of volumes in each shell") - + PhaseEncoding::ExportOptions; + + PhaseEncoding::ExportOptions + + Option ("petable", "print the phase encoding table"); } @@ -136,6 +137,21 @@ void print_strides (const Header& header) std::cout << buffer << "\n"; } +void print_shells (const Header& header, const bool shells, const bool shellcounts) +{ + DWI::Shells dwshells (DWI::parse_DW_scheme (header)); + if (shells) { + for (size_t i = 0; i < dwshells.count(); i++) + std::cout << dwshells[i].get_mean() << " "; + std::cout << "\n"; + } + if (shellcounts) { + for (size_t i = 0; i < dwshells.count(); i++) + std::cout << dwshells[i].count() << " "; + std::cout << "\n"; + } +} + void print_properties (const Header& header, const std::string& key, const size_t indent = 0) { if (lowercase (key) == "all") { @@ -195,9 +211,10 @@ void run () const bool shells = get_options("shells") .size(); const bool shellcounts = get_options("shellcounts") .size(); const bool raw_dwgrad = get_options("raw_dwgrad") .size(); + const bool petable = get_options("petable") .size(); const bool print_full_header = !(format || ndim || size || vox || datatype || stride || - offset || multiplier || properties.size() || transform || dwgrad || export_grad || shells || shellcounts || export_pe); + offset || multiplier || properties.size() || transform || dwgrad || export_grad || shells || shellcounts || export_pe || petable); for (size_t i = 0; i < argument.size(); ++i) { @@ -208,6 +225,7 @@ void run () DWI::set_DW_scheme (header, DWI::get_valid_DW_scheme (header, true)); Eigen::IOFormat fmt(Eigen::FullPrecision, 0, ", ", "\n", "", "", "", "\n"); + if (format) std::cout << header.format() << "\n"; if (ndim) std::cout << header.ndim() << "\n"; if (size) print_dimensions (header); @@ -218,19 +236,9 @@ void run () if (multiplier) std::cout << header.intensity_scale() << "\n"; if (transform) std::cout << header.transform().matrix().format(fmt); if (dwgrad) std::cout << DWI::parse_DW_scheme (header) << "\n"; - if (shells || shellcounts) { - DWI::Shells dwshells (DWI::parse_DW_scheme (header)); - if (shells) { - for (size_t i = 0; i < dwshells.count(); i++) - std::cout << dwshells[i].get_mean() << " "; - std::cout << "\n"; - } - if (shellcounts) { - for (size_t i = 0; i < dwshells.count(); i++) - std::cout << dwshells[i].count() << " "; - std::cout << "\n"; - } - } + if (shells || shellcounts) print_shells (header, shells, shellcounts); + if (petable) std::cout << PhaseEncoding::get_scheme (header) << "\n"; + for (size_t n = 0; n < properties.size(); ++n) print_properties (header, properties[n][0]); diff --git a/docs/reference/commands/mrinfo.rst b/docs/reference/commands/mrinfo.rst index bb4e767947..104a257917 100644 --- a/docs/reference/commands/mrinfo.rst +++ b/docs/reference/commands/mrinfo.rst @@ -81,6 +81,8 @@ Options for exporting phase-encode tables - **-export_pe_eddy config indices** export phase-encoding information to an EDDY-style config / index file pair +- **-petable** print the phase encoding table + Standard options ^^^^^^^^^^^^^^^^ diff --git a/lib/file/dicom/mapper.cpp b/lib/file/dicom/mapper.cpp index 7c01b98d4e..c5177ff809 100644 --- a/lib/file/dicom/mapper.cpp +++ b/lib/file/dicom/mapper.cpp @@ -108,7 +108,7 @@ namespace MR { const Image& image (*(*series[0])[0]); if (std::isfinite (image.echo_time)) - H.keyval()["EchoTime"] = str (0.001 * image.echo_time, 6); + H.keyval()["echotime"] = str (0.001 * image.echo_time, 6); size_t nchannels = image.frames.size() ? 1 : image.data_size / (image.dim[0] * image.dim[1] * (image.bits_alloc/8)); if (nchannels > 1) diff --git a/lib/file/json_utils.cpp b/lib/file/json_utils.cpp index 62ad885036..acc97383c8 100644 --- a/lib/file/json_utils.cpp +++ b/lib/file/json_utils.cpp @@ -19,6 +19,7 @@ #include "exception.h" #include "header.h" +#include "mrtrix.h" #include "file/ofstream.h" namespace MR @@ -44,7 +45,7 @@ namespace MR for (auto i = json.cbegin(); i != json.cend(); ++i) { // Only load simple parameters at the first level if (i->is_primitive()) - H.keyval().insert (std::make_pair (i.key(), str(i.value()))); + H.keyval().insert (std::make_pair (lowercase (i.key()), str(i.value()))); } } diff --git a/lib/phase_encoding.cpp b/lib/phase_encoding.cpp index 39fef145ca..954dab95b7 100644 --- a/lib/phase_encoding.cpp +++ b/lib/phase_encoding.cpp @@ -100,8 +100,9 @@ namespace MR PE(row, col) = values[col]; } } else { - const auto it_dir = header.keyval().find ("PhaseEncodingDirection"); - const auto it_time = header.keyval().find ("TotalReadoutTime"); + // Header entries are cast to lowercase at some point + const auto it_dir = header.keyval().find ("phaseencodingdirection"); + const auto it_time = header.keyval().find ("totalreadouttime"); if (it_dir != header.keyval().end() && it_time != header.keyval().end()) { Eigen::Matrix row; row.head<3>() = id2dir (it_dir->second); diff --git a/lib/phase_encoding.h b/lib/phase_encoding.h index 6f6d42e098..1ce99d6a71 100644 --- a/lib/phase_encoding.h +++ b/lib/phase_encoding.h @@ -62,9 +62,9 @@ namespace MR void check (const Header& header, const MatrixType& PE) { check (PE); - - if (((header.ndim() > 3) ? header.size (3) : 1) != (int) PE.rows()) - throw Exception ("Number of volumes in image \"" + header.name() + "\" does not match that in phase encoding table"); + const ssize_t num_volumes = (header.ndim() > 3) ? header.size (3) : 1; + if (num_volumes != PE.rows()) + throw Exception ("Number of volumes in image \"" + header.name() + "\" (" + str(num_volumes) + ") does not match that in phase encoding table (" + str(PE.rows()) + ")"); } @@ -91,8 +91,8 @@ namespace MR { if (!PE.rows()) { header.keyval().erase ("pe_scheme"); - header.keyval().erase ("PhaseEncodingDirection"); - header.keyval().erase ("TotalReadoutTime"); + header.keyval().erase ("phaseencodingdirection"); + header.keyval().erase ("totalreadouttime"); return; } PhaseEncoding::check (header, PE); @@ -113,16 +113,16 @@ namespace MR } if (variation) { header.keyval()["pe_scheme"] = pe_scheme; - header.keyval().erase ("PhaseEncodingDirection"); - header.keyval().erase ("TotalReadoutTime"); + header.keyval().erase ("phaseencodingdirection"); + header.keyval().erase ("totalreadouttime"); } else { header.keyval().erase ("pe_scheme"); const Eigen::Vector3 dir { PE(0, 0), PE(0, 1), PE(0, 2) }; - header.keyval()["PhaseEncodingDirection"] = dir2id (dir); + header.keyval()["phaseencodingdirection"] = dir2id (dir); if (PE.cols() >= 4) - header.keyval()["TotalReadoutTime"] = PE(0, 3); + header.keyval()["totalreadouttime"] = str(PE(0, 3), 3); else - header.keyval().erase ("TotalReadoutTime"); + header.keyval().erase ("totalreadouttime"); } } diff --git a/scripts/dwipreproc b/scripts/dwipreproc index 4f3d7e1e6e..473865f94e 100755 --- a/scripts/dwipreproc +++ b/scripts/dwipreproc @@ -17,19 +17,21 @@ import math, os, sys import lib.app, lib.cmdlineParser -from lib.binaryInPath import binaryInPath -from lib.delFile import delFile -from lib.errorMessage import errorMessage -from lib.getFSLEddyPath import getFSLEddyPath -from lib.getFSLSuffix import getFSLSuffix -from lib.getHeaderInfo import getHeaderInfo -from lib.getUserPath import getUserPath -from lib.getPEDir import getPEDir -from lib.imagesMatch import imagesMatch -from lib.isWindows import isWindows -from lib.printMessage import printMessage -from lib.runCommand import runCommand -from lib.warnMessage import warnMessage +from lib.binaryInPath import binaryInPath +from lib.delFile import delFile +from lib.errorMessage import errorMessage +from lib.getFSLEddyPath import getFSLEddyPath +from lib.getFSLSuffix import getFSLSuffix +from lib.getHeaderInfo import getHeaderInfo +from lib.getHeaderProperty import getHeaderProperty +from lib.getUserPath import getUserPath +from lib.getPEDir import getPEDir +from lib.getPEScheme import getPEScheme +from lib.imagesMatch import imagesMatch +from lib.isWindows import isWindows +from lib.printMessage import printMessage +from lib.runCommand import runCommand +from lib.warnMessage import warnMessage lib.app.author = 'Robert E. Smith (robert.smith@florey.edu.au)' lib.app.addCitation('', 'Andersson, J. L. & Sotiropoulos, S. N. An integrated approach to correction for off-resonance effects and subject movement in diffusion MR imaging. NeuroImage, 2015, 125, 1063-1078', True) @@ -131,13 +133,15 @@ lib.app.gotoTempDir() # Get information on the input images, particularly so that their validity can be checked dwi_size = [ int(s) for s in getHeaderInfo('dwi.mif', 'size').split() ] -dwi_pe_scheme = [ line.split() for line in getHeaderProperty('dwi.mif', 'pe_scheme').split('\n') ] +dwi_pe_scheme = getPEScheme('dwi.mif') if lib.app.args.topup_images: topup_size = [ int[s] for s in getHeaderInfo('topup_in.mif', 'size').split() ] if not len(topup_size) == 4: errorMessage('File provided using -topup_images option must contain more than one image volume') - topup_pe_scheme = [ line.split() for line in getHeaderProperty('topup_in.mif', 'pe_scheme').split('\n') ] -grad = [ float(f) for f in (line.split() for line in getHeaderInfo('dwi.mif', 'dwgrad').split('\n')) ] + topup_pe_scheme = getPEScheme('topup_in.mif') +grad = getHeaderInfo('dwi.mif', 'dwgrad').split('\n') +grad = [ line.split() for line in grad ] +grad = [ (float(f) for f in line) for line in grad ] stride = getHeaderInfo('dwi.mif', 'stride') num_volumes = 1 if len(dwi_size) == 4: @@ -149,7 +153,7 @@ if len(dwi_size) == 4: # such information is present in the header, let's grab it here manual_pe_dir = None if lib.app.args.pe_dir: - manual_pe_dir = getPEDir(lib.app.args.pe_dir) + manual_pe_dir = [ float(i) for i in getPEDir(lib.app.args.pe_dir) ] manual_trt = None if lib.app.args.readout_time: manual_trt = float(lib.app.args.readout_time) @@ -164,7 +168,7 @@ if not len(grad) == num_volumes: -do_topup = not PE_scheme == 'None' +do_topup = not PE_design == 'None' @@ -182,14 +186,14 @@ if manual_pe_dir: # Still construct the manual PE scheme even with 'None' or 'Pair': # there may be information in the header that we need to compare against - if PE_scheme == 'None': - line = manual_pe_dir.extend(trt) - dwi_manual_pe_scheme = ' '.join(str(i) for i in line) * num_volumes + if PE_design == 'None': + line = manual_pe_dir + line.append(trt) + dwi_manual_pe_scheme = [ line ] * num_volumes # With 'Pair', also need to construct the manual scheme for topup_images - elif PE_scheme == 'Pair': - line = manual_pe_dir.extend(trt) - dwi_manual_pe_scheme = ' '.join(str(i) for i in line) * num_volumes + elif PE_design == 'Pair': + dwi_manual_pe_scheme = [ manual_pe_dir.append(trt) ] * num_volumes num_topup_volumes = topup_size[3] # Assume that first half of volumes have same direction as series; # second half have the opposite direction @@ -200,16 +204,17 @@ if manual_pe_dir: # If -rpe_all, need to scan through grad and figure out the pairings # This will be required if relying on user-specified phase encode direction # It will also be required at the end of the script for the manual recombination - elif PE_scheme == 'All': + elif PE_design == 'All': grad_matchings = [ num_volumes for row in grad ] grad_pairs = [ ] for index1 in enumerate(grad): if grad_matchings[index1] == num_volumes: # As yet unpaired for index2 in range(index1+1, num_volumes): - if grad[index1] == grad[index2]: - grad_matchings[index1] = index2; - grad_matchings[index2] = index1; - grad_pairs.append([index1, index2]) + if grad_matchings[index2] == num_volumes: # Also as yet unpaired + if grad[index1] == grad[index2]: + grad_matchings[index1] = index2; + grad_matchings[index2] = index1; + grad_pairs.append([index1, index2]) if not len(grad_pairs) == num_volumes/2: errorMessage('Unable to determine matching DWI volume pairs for reversed phase-encode combination') # Construct manual PE scheme here: @@ -218,9 +223,14 @@ if manual_pe_dir: dwi_manual_pe_scheme = [ ] for index in range(0, num_volumes): if grad_matchings[index] > index: - dwi_manual_pe_scheme.append(manual_pe_dir.extend(trt)) + dwi_manual_pe_scheme.append(manual_pe_dir.append(trt)) else: - dwi_manual_pe_scheme.append( [ -i for i in manual_pe_dir ].extend(trt)) + dwi_manual_pe_scheme.append( [ -i for i in manual_pe_dir ].append(trt)) + +else: # No manual phase encode direction defined + + if not PE_design == 'Header': + errorMessage('If not using -rpe_header, phase encoding direction must be provided using the -pe_dir option') @@ -336,7 +346,7 @@ if lib.app.args.topup_images: if not topup_pe_scheme: errorMessage('No phase-encoding information present in topup image header'); -else: # No topup images explicitly provided: In some cases, can extract appropriate b=0 images from DWI +elif not PE_design == 'None': # No topup images explicitly provided: In some cases, can extract appropriate b=0 images from DWI # If using 'All' or 'Header', and haven't been given any topup images, need to extract the b=0 volumes from the series, # preserving phase-encoding information while doing so @@ -349,14 +359,17 @@ else: # No topup images explicitly provided: In some cases, can extract appropri f.write(' '.join(str(i) for i in line)) runCommand('mrconvert series.mif -import_pe_table series_manual_pe_scheme.txt - | dwiextract - topup_in.mif -bzero') elif PE_design == 'Header': - runCommand('dwiextract dwi.mif -bzero topup_in.mif') - else: - errorMessage('Script error: Shouldn\'t be here') - # If there's no contrast remaining in the phase-encoding scheme, it'll be written to - # PhaseEncodingDirection and TotalReadoutTime rather than pe_scheme - if not getHeaderProperty('topup_in.mif', 'pe_scheme'): - runCommand('mrconvert bzeros.mif topup_in.nii -stride -1,+2,+3,+4 -export_pe_table topup_datain.txt') - delFile('topup_in.mif') + runCommand('dwiextract dwi.mif -bzero bzeros.mif') + # If there's no contrast remaining in the phase-encoding scheme, it'll be written to + # PhaseEncodingDirection and TotalReadoutTime rather than pe_scheme + # In this scenario, we will be unable to run topup + if getHeaderProperty('bzeros.mif', 'pe_scheme'): + runCommand('mrconvert bzeros.mif topup_in.mif -stride -1,+2,+3,+4 -export_pe_table topup_datain.txt') + else: + warnMessage('DWI header indicates no phase encoding contrast in b=0 images; proceeding without inhomogeneity field estimation') + do_topup = False + delFile('bzeros.mif') + @@ -365,6 +378,16 @@ runCommand('mrinfo dwi.mif -export_grad_mrtrix grad.b') +import_dwi_pe_table_option = '' +print (dwi_manual_pe_scheme) +if dwi_manual_pe_scheme: + with open('dwi_manual_pe_scheme.txt', 'w') as f: + for line in dwi_manual_pe_scheme: + f.write(' '.join( [ str(f) for f in line ] ) + '\n') + import_dwi_pe_table_option = ' -import_pe_table dwi_manual_pe_scheme.txt' + + + eddy_in_topup_option = '' if do_topup: @@ -392,12 +415,7 @@ if do_topup: # Apply the warp field to the input image series to get an initial corrected volume estimate # applytopup can't receive the complete DWI input and correct it as a whole, because the phase-encoding # details may vary between volumes - import_dwi_pe_table_option = '' if dwi_manual_pe_scheme: - with open('dwi_manual_pe_scheme.txt', 'w') as f: - for line in dwi_manual_pe_scheme: - f.write(' '.join(line)) - import_dwi_pe_table_option = ' -import_pe_table dwi_manual_pe_scheme.txt' runCommand('mrconvert dwi.mif' + import_dwi_pe_table_option + ' - | mrinfo - -export_pe_eddy applytopup_config.txt applytopup_indices.txt') else: runCommand('mrinfo ' + topup_in_path + ' -export_pe_eddy applytopup_config.txt applytopup_indices.txt') @@ -452,16 +470,17 @@ if not os.path.isfile(bvecs_path): # This could be either due to use of -rpe_all option, or just due to the data provided with -rpe_header # Rather than trying to re-use the code that was used in the case of -rpe_all, run fresh code # The phase-encoding scheme needs to be checked also -volume_matchings = [ num_volumes for row in grad ] +volume_matchings = [ num_volumes ] * num_volumes volume_pairs = [ ] -for index1 in enumerate(grad): +for index1 in range(num_volumes): if volume_matchings[index1] == num_volumes: # As yet unpaired for index2 in range(index1+1, num_volumes): - # Here, need to check both gradient matching and reversed phase-encode direction - if not max(abs(dwi_pe_scheme[index1][0:3] + dwi_pe_scheme[index2][0:3])) and grad[index1] == grad[index2]: - volume_matchings[index1] = index2; - volume_matchings[index2] = index1; - volume_pairs.append([index1, index2]) + if volume_matchings[index2] == num_volumes: # Also as yet unpaired + # Here, need to check both gradient matching and reversed phase-encode direction + if not max(abs(dwi_pe_scheme[index1][0:3] + dwi_pe_scheme[index2][0:3])) and grad[index1] == grad[index2]: + volume_matchings[index1] = index2; + volume_matchings[index2] = index1; + volume_pairs.append([index1, index2]) if not len(volume_pairs) == num_volumes/2: diff --git a/scripts/lib/binaryInPath.py b/scripts/lib/binaryInPath.py index 67fb12b972..5f914e978a 100644 --- a/scripts/lib/binaryInPath.py +++ b/scripts/lib/binaryInPath.py @@ -1,5 +1,6 @@ def binaryInPath(cmdname): import os + from lib.debugMessage import debugMessage for path_dir in os.environ["PATH"].split(os.pathsep): path_dir = path_dir.strip('"') full_path = os.path.join(path_dir, cmdname) diff --git a/scripts/lib/getFSLEddyPath.py b/scripts/lib/getFSLEddyPath.py index 732ee1fb51..db6dbbbbbd 100644 --- a/scripts/lib/getFSLEddyPath.py +++ b/scripts/lib/getFSLEddyPath.py @@ -1,6 +1,7 @@ def getFSLEddyPath(cuda): import os from lib.binaryInPath import binaryInPath + from lib.debugMessage import debugMessage from lib.errorMessage import errorMessage from lib.warnMessage import warnMessage if cuda: diff --git a/scripts/lib/getFSLSuffix.py b/scripts/lib/getFSLSuffix.py index 38c4c8543a..3e8c052b55 100644 --- a/scripts/lib/getFSLSuffix.py +++ b/scripts/lib/getFSLSuffix.py @@ -1,6 +1,7 @@ def getFSLSuffix(): import os, sys - from lib.warnMessage import warnMessage + from lib.debugMessage import debugMessage + from lib.warnMessage import warnMessage fsl_output_type = os.environ.get('FSLOUTPUTTYPE', '') if fsl_output_type == 'NIFTI': debugMessage('NIFTI -> .nii') diff --git a/scripts/lib/getPEScheme.py b/scripts/lib/getPEScheme.py new file mode 100644 index 0000000000..2b8a04c5cb --- /dev/null +++ b/scripts/lib/getPEScheme.py @@ -0,0 +1,20 @@ +def getPEScheme(path): + import lib.app, os, subprocess + from lib.debugMessage import debugMessage + from lib.printMessage import printMessage + command = [ 'mrinfo', path, '-petable' ] + if lib.app.verbosity > 1: + printMessage('Command: \'' + ' '.join(command) + '\' (piping data to local storage)') + proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=None) + result, err = proc.communicate() + result = result.rstrip().decode('utf-8') + if result: + result = [ [ float(f) for f in line.split() ] for line in result.split('\n') ] + if lib.app.verbosity > 1: + if not result: + printMessage('Result: No phase encoding table found') + else: + printMessage('Result: ' + str(len(result)) + ' x ' + str(len(result[0])) + ' table') + debugMessage(str(result)) + return result + diff --git a/scripts/lib/getTempImagePath.py b/scripts/lib/getTempFilePath.py similarity index 81% rename from scripts/lib/getTempImagePath.py rename to scripts/lib/getTempFilePath.py index a573b43fd4..e986482fa2 100644 --- a/scripts/lib/getTempImagePath.py +++ b/scripts/lib/getTempFilePath.py @@ -1,19 +1,19 @@ -def getTempImagePath(suffix): +def getTempFilePath(suffix): import os, random, string from lib.debugMessage import debugMessage from lib.readMRtrixConfSetting import readMRtrixConfSetting dir_path = readMRtrixConfSetting('TmpFileDir') - if not dir_path + if not dir_path: dir_path = '.' prefix = readMRtrixConfSetting('TmpFilePrefix') - if not prefix + if not prefix: prefix = os.path.basename(sys.argv[0]) + '-tmp-' full_path = dir_path - if not suffix + if not suffix: suffix = 'mif' - while os.path.exists(full_path) + while os.path.exists(full_path): random_string = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(6)) - full_path = os.path.join(dir_path, prefix + random_string + '.' + suffix + full_path = os.path.join(dir_path, prefix + random_string + '.' + suffix) debugMessage(full_path) return full_path diff --git a/scripts/lib/runCommand.py b/scripts/lib/runCommand.py index 2d7fb1eff3..2de4995ce8 100644 --- a/scripts/lib/runCommand.py +++ b/scripts/lib/runCommand.py @@ -163,3 +163,4 @@ def runCommand(cmd, exitOnError=True): outfile.write(cmd + '\n') return (return_stdout, return_stderr) + From 7e53e8256daa5df1425d7b5cccba7d11227e3466 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 7 Sep 2016 16:43:12 +1000 Subject: [PATCH 101/723] added new commands 3tnormalise and mrmodel --- cmd/3tnormalise.cpp | 110 ++++++++++++++++++++++++++++++++++++ cmd/mrmodel.cpp | 133 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 cmd/3tnormalise.cpp create mode 100644 cmd/mrmodel.cpp diff --git a/cmd/3tnormalise.cpp b/cmd/3tnormalise.cpp new file mode 100644 index 0000000000..3db32ae1af --- /dev/null +++ b/cmd/3tnormalise.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + + +#include "command.h" +#include "image.h" +#include "algo/loop.h" +#include "adapter/extract.h" +#include "filter/optimal_threshold.h" + +using namespace MR; +using namespace App; + + + +void usage () +{ + AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; + + DESCRIPTION + + "Globally normalise three tissue (wm, gm, csf) comparments from multi-tissue CSD " + "such that their sum within each voxel is as close to 1 as possible. " + "This involves solving for a single scale factor for each compartment map."; + + ARGUMENTS + + Argument ("wm", "the white matter compartment (FOD) image").type_image_in() + + Argument ("gm", "the grey matter compartment image").type_image_in() + + Argument ("csf", "the cerebral spinal fluid comparment image").type_image_in() + + Argument ("wm_out", "the output white matter compartment (FOD) image").type_image_out() + + Argument ("gm_out", "the output grey matter compartment image").type_image_out() + + Argument ("csf_out", "the output cerebral spinal fluid comparment image").type_image_out(); + + OPTIONS + + Option ("mask", "define the mask to compute the normalisation within. If not supplied this is estimated automatically") + + Argument ("image").type_image_in (); +} + + +void run () +{ + auto wm = Image::open (argument[0]); + auto gm = Image::open (argument[1]); + auto csf = Image::open (argument[2]); + + check_dimensions (wm, gm, 0, 3); + check_dimensions (csf, gm); + + auto wm_out = Image::create (argument[3], wm); + auto gm_out = Image::create (argument[4], gm); + auto csf_out = Image::create (argument[5], csf); + + auto wm_dc = Adapter::make (wm, 3, std::vector (1, 0)); + + Image mask; + auto opt = get_options("mask"); + if (opt.size()) { + mask = Image::open (opt[0][0]); + } else { + auto summed = Image::scratch (gm); + for (auto i = Loop (gm) (gm, csf, wm_dc, summed); i; ++i) + summed.value() = gm.value() + csf.value() + wm_dc.value(); + Filter::OptimalThreshold threshold_filter (summed); + mask = Image::scratch (threshold_filter); + threshold_filter (summed, mask); + } + size_t num_voxels = 0; + for (auto i = Loop (mask) (mask); i; ++i) { + if (mask.value()) + num_voxels++; + } + + { + ProgressBar progress ("normalising tissue compartments..."); + Eigen::MatrixXf X (num_voxels, 3); + Eigen::MatrixXf y (num_voxels, 1); + y.setOnes(); + size_t counter = 0; + for (auto i = Loop (gm) (gm, csf, wm_dc, mask); i; ++i) { + if (mask.value()) { + X (counter, 0) = gm.value(); + X (counter, 1) = csf.value(); + X (counter++, 2) = wm_dc.value(); + } + } + progress++; + Eigen::MatrixXf w = X.jacobiSvd (Eigen::ComputeThinU | Eigen::ComputeThinV).solve(y); + progress++; + for (auto i = Loop (gm) (gm, csf, gm_out, csf_out); i; ++i) { + gm_out.value() = gm.value() * w(0,0); + csf_out.value() = csf.value() * w(1,0); + } + progress++; + for (auto i = Loop (wm) (wm, wm_out); i; ++i) + wm_out.value() = wm.value() * w(2,0); + } +} + diff --git a/cmd/mrmodel.cpp b/cmd/mrmodel.cpp new file mode 100644 index 0000000000..9f09c2bfe3 --- /dev/null +++ b/cmd/mrmodel.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + + +#include "command.h" +#include "image.h" +#include "algo/loop.h" +#include "adapter/extract.h" +#include "filter/optimal_threshold.h" +#include "transform.h" + +using namespace MR; +using namespace App; + + + +void usage () +{ + AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) & Rami Tabbara (rami.tabbara@florey.edu.au)"; + + DESCRIPTION + + "Model an input image using low frequency 3D polynomial basis functions. " + "This command was designed to estimate a DWI bias field using the sum of normalised multi-tissue CSD compartments."; + + ARGUMENTS + + Argument ("input", "the input image").type_image_in() + + Argument ("output", "the output image representing the fit").type_image_out(); + + + OPTIONS + + Option ("mask", "use only voxels within the supplied mask for the model fit. If not supplied this command will compute a mask") + + Argument ("image").type_image_in (); +} + + +Eigen::VectorXf basis_function (Eigen::Vector3 pos) { + float x = (float)pos[0]; + float y = (float)pos[1]; + float z = (float)pos[2]; + Eigen::VectorXf basis(19); + basis(0) = 1.0; + basis(1) = x; + basis(2) = y; + basis(3) = z; + basis(4) = x * y; + basis(5) = x * z; + basis(6) = y * z; + basis(7) = x * x; + basis(8) = y * y; + basis(9)= z * x; + basis(10)= x * x * y; + basis(11) = x * x * z; + basis(12) = y * y * x; + basis(13) = y * y * z; + basis(14) = z * z * x; + basis(15) = z * z * y; + basis(16) = x * x * x; + basis(17) = y * y * y; + basis(18) = z * z * z; + return basis; +} + + + + + +void run () +{ + auto input = Image::open (argument[0]); + auto output = Image::create (argument[1], input); + + if (input.ndim() != 3) + throw Exception ("input image must be 3D"); + + Image mask; + auto opt = get_options("mask"); + if (opt.size()) { + mask = Image::open (opt[0][0]); + } else { + Filter::OptimalThreshold threshold_filter (input); + mask = Image::scratch (threshold_filter); + threshold_filter (input, mask); + } + + size_t num_voxels = 0; + for (auto i = Loop (mask) (mask); i; ++i) { + if (mask.value()) + num_voxels++; + } + + Eigen::MatrixXf X (num_voxels, 18); + Eigen::VectorXf y (num_voxels); + y.setOnes(); + + + { + ProgressBar progress ("fitting model..."); + size_t counter = 0; + Transform transform (input); + for (auto i = Loop (mask) (input, mask); i; ++i) { + if (mask.value()) { + y [counter] = input.value(); + Eigen::Vector3 vox (input.index(0), input.index(1), input.index(2)); + Eigen::Vector3 pos = transform.voxel2scanner * vox; + X.row (counter++) = basis_function (pos); + } + } + progress++; + + Eigen::VectorXf w = X.jacobiSvd (Eigen::ComputeThinU | Eigen::ComputeThinV).solve(y); + + progress++; + for (auto i = Loop (output) (output); i; ++i) { + Eigen::Vector3 vox (output.index(0), output.index(1), output.index(2)); + Eigen::Vector3 pos = transform.voxel2scanner * vox; + output.value() = basis_function (pos).dot (w); + } + } +} + From bdd1fc880945c1705a06e41f00c96c19a4ab2e5b Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 7 Sep 2016 16:44:29 +1000 Subject: [PATCH 102/723] renamed 3tnormalise to 3ttnormalise --- cmd/{3tnormalise.cpp => 3ttnormalise.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cmd/{3tnormalise.cpp => 3ttnormalise.cpp} (100%) diff --git a/cmd/3tnormalise.cpp b/cmd/3ttnormalise.cpp similarity index 100% rename from cmd/3tnormalise.cpp rename to cmd/3ttnormalise.cpp From 63620a899ba9a8a44b1cec66a046b02f1099aad4 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 7 Sep 2016 16:45:09 +1000 Subject: [PATCH 103/723] renamed mrmodel to mrmodelfield --- cmd/{mrmodel.cpp => mrmodelfield.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cmd/{mrmodel.cpp => mrmodelfield.cpp} (100%) diff --git a/cmd/mrmodel.cpp b/cmd/mrmodelfield.cpp similarity index 100% rename from cmd/mrmodel.cpp rename to cmd/mrmodelfield.cpp From 974fc448d25d977d86f0c0508974c2c52eb94e7a Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 7 Sep 2016 17:55:36 +1000 Subject: [PATCH 104/723] Even more further work on dwipreproc and phase encoding data handling - dwiextract: Fix selection of volumes based on phase encoding information. - Restore BIDS-defined header key/value entries to use capitalization. Exporting without capitalization may not work with other software packages. Instead, the MRtrix image formats will no longer cast keys to lowercase when importing. - Fix export of phase encoding table to eddy config/index format. - dwipreproc: Too many fixes to count. --- cmd/dwiextract.cpp | 7 ++- lib/file/dicom/mapper.cpp | 2 +- lib/file/json_utils.cpp | 2 +- lib/formats/mrtrix_utils.h | 18 +++--- lib/mrtrix.cpp | 3 +- lib/mrtrix.h | 9 +-- lib/phase_encoding.cpp | 4 +- lib/phase_encoding.h | 39 ++++++------- scripts/dwipreproc | 93 ++++++++++++++++++------------ src/dwi/tractography/file_base.cpp | 4 +- 10 files changed, 101 insertions(+), 80 deletions(-) diff --git a/cmd/dwiextract.cpp b/cmd/dwiextract.cpp index 8b5b20eb2e..b7af425882 100644 --- a/cmd/dwiextract.cpp +++ b/cmd/dwiextract.cpp @@ -80,6 +80,11 @@ void run() 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 (int i = 0; i != grad.rows(); ++i) + volumes.push_back (i); } opt = get_options ("pe"); @@ -100,7 +105,7 @@ void run() } } if (filter.size() == 4) { - if (std::abs (pe_scheme(i, 3) - filter[3]) > 5e-4) { + if (std::abs (pe_scheme(i, 3) - filter[3]) > 5e-3) { keep = false; break; } diff --git a/lib/file/dicom/mapper.cpp b/lib/file/dicom/mapper.cpp index c5177ff809..7c01b98d4e 100644 --- a/lib/file/dicom/mapper.cpp +++ b/lib/file/dicom/mapper.cpp @@ -108,7 +108,7 @@ namespace MR { const Image& image (*(*series[0])[0]); if (std::isfinite (image.echo_time)) - H.keyval()["echotime"] = str (0.001 * image.echo_time, 6); + H.keyval()["EchoTime"] = str (0.001 * image.echo_time, 6); size_t nchannels = image.frames.size() ? 1 : image.data_size / (image.dim[0] * image.dim[1] * (image.bits_alloc/8)); if (nchannels > 1) diff --git a/lib/file/json_utils.cpp b/lib/file/json_utils.cpp index acc97383c8..fa190963a6 100644 --- a/lib/file/json_utils.cpp +++ b/lib/file/json_utils.cpp @@ -45,7 +45,7 @@ namespace MR for (auto i = json.cbegin(); i != json.cend(); ++i) { // Only load simple parameters at the first level if (i->is_primitive()) - H.keyval().insert (std::make_pair (lowercase (i.key()), str(i.value()))); + H.keyval().insert (std::make_pair (i.key(), str(i.value()))); } } diff --git a/lib/formats/mrtrix_utils.h b/lib/formats/mrtrix_utils.h index ee432ed97a..e97c536d5c 100644 --- a/lib/formats/mrtrix_utils.h +++ b/lib/formats/mrtrix_utils.h @@ -66,20 +66,20 @@ namespace MR std::string dtype, layout; std::vector dim; std::vector vox, scaling; - std::vector> transform, dw_scheme; + std::vector> transform; std::string key, value; while (next_keyvalue (kv, key, value)) { - key = lowercase (key); - if (key == "dim") dim = parse_ints (value); - else if (key == "vox") vox = parse_floats (value); - else if (key == "layout") layout = value; - else if (key == "datatype") dtype = value; - else if (key == "scaling") scaling = parse_floats (value); - else if (key == "transform") + const std::string lkey = lowercase (key); + if (lkey == "dim") dim = parse_ints (value); + else if (lkey == "vox") vox = parse_floats (value); + else if (lkey == "layout") layout = value; + else if (lkey == "datatype") dtype = value; + else if (lkey == "scaling") scaling = parse_floats (value); + else if (lkey == "transform") transform.push_back (parse_floats (value)); else if (key.size() && value.size()) - add_line (H.keyval()[key], value); + add_line (H.keyval()[key], value); // Preserve capitalization if not a compulsory key } if (dim.empty()) diff --git a/lib/mrtrix.cpp b/lib/mrtrix.cpp index 41a6774fe6..32b78121ec 100644 --- a/lib/mrtrix.cpp +++ b/lib/mrtrix.cpp @@ -77,8 +77,7 @@ namespace MR do { end = spec.find_first_of (",:", start); std::string token (strip (spec.substr (start, end-start))); - lowercase (token); - if (token == "end") { + if (lowercase (token) == "end") { if (last == std::numeric_limits::max()) throw Exception ("value of \"end\" is not known in number sequence \"" + spec + "\""); num[i] = last; diff --git a/lib/mrtrix.h b/lib/mrtrix.h index 838e8f27f8..8fd32b8332 100644 --- a/lib/mrtrix.h +++ b/lib/mrtrix.h @@ -134,13 +134,14 @@ namespace MR T value; stream >> value; if (stream.fail()) { - if (lowercase (string) == "nan") + const std::string lstring = lowercase (string); + if (lstring == "nan") return std::numeric_limits::quiet_NaN(); - else if (lowercase (string) == "-nan") + else if (lstring == "-nan") return -std::numeric_limits::quiet_NaN(); - else if (lowercase (string) == "inf") + else if (lstring == "inf") return std::numeric_limits::infinity(); - else if (lowercase (string) == "-inf") + else if (lstring == "-inf") return -std::numeric_limits::infinity(); throw Exception ("error converting string \"" + string + "\""); } diff --git a/lib/phase_encoding.cpp b/lib/phase_encoding.cpp index 954dab95b7..70f7f3610b 100644 --- a/lib/phase_encoding.cpp +++ b/lib/phase_encoding.cpp @@ -101,8 +101,8 @@ namespace MR } } else { // Header entries are cast to lowercase at some point - const auto it_dir = header.keyval().find ("phaseencodingdirection"); - const auto it_time = header.keyval().find ("totalreadouttime"); + const auto it_dir = header.keyval().find ("PhaseEncodingDirection"); + const auto it_time = header.keyval().find ("TotalReadoutTime"); if (it_dir != header.keyval().end() && it_time != header.keyval().end()) { Eigen::Matrix row; row.head<3>() = id2dir (it_dir->second); diff --git a/lib/phase_encoding.h b/lib/phase_encoding.h index 1ce99d6a71..5b1f73fe89 100644 --- a/lib/phase_encoding.h +++ b/lib/phase_encoding.h @@ -91,8 +91,8 @@ namespace MR { if (!PE.rows()) { header.keyval().erase ("pe_scheme"); - header.keyval().erase ("phaseencodingdirection"); - header.keyval().erase ("totalreadouttime"); + header.keyval().erase ("PhaseEncodingDirection"); + header.keyval().erase ("TotalReadoutTime"); return; } PhaseEncoding::check (header, PE); @@ -113,16 +113,16 @@ namespace MR } if (variation) { header.keyval()["pe_scheme"] = pe_scheme; - header.keyval().erase ("phaseencodingdirection"); - header.keyval().erase ("totalreadouttime"); + header.keyval().erase ("PhaseEncodingDirection"); + header.keyval().erase ("TotalReadoutTime"); } else { header.keyval().erase ("pe_scheme"); const Eigen::Vector3 dir { PE(0, 0), PE(0, 1), PE(0, 2) }; - header.keyval()["phaseencodingdirection"] = dir2id (dir); + header.keyval()["PhaseEncodingDirection"] = dir2id (dir); if (PE.cols() >= 4) - header.keyval()["totalreadouttime"] = str(PE(0, 3), 3); + header.keyval()["TotalReadoutTime"] = str(PE(0, 3), 3); else - header.keyval().erase ("totalreadouttime"); + header.keyval().erase ("TotalReadoutTime"); } } @@ -160,24 +160,23 @@ namespace MR if (PE.cols() != 4) throw Exception ("Phase-encoding matrix requires 4 columns to convert to eddy format"); config.resize (0, 0); - indices.resize (PE.rows()); + indices = Eigen::Array::Constant (PE.rows(), PE.rows()); for (ssize_t PE_row = 0; PE_row != PE.rows(); ++PE_row) { - for (ssize_t config_row = 0; config_row != config.rows(); ++config_row) { - if (PE.template block<1,3>(PE_row, 0).isApprox (config.block<1,3>(config_row, 0)) - && ((std::abs(PE(PE_row, 3) - config(config_row, 3))) / (PE(PE_row, 3) + config(config_row, 3)) < 1e-3)) { - + bool dir_match = PE.template block<1,3>(PE_row, 0).isApprox (config.block<1,3>(config_row, 0)); + bool time_match = std::abs (PE(PE_row, 3) - config(config_row, 3)) < 1e-3; + if (dir_match && time_match) { // FSL-style index file indexes from 1 indices[PE_row] = config_row + 1; - continue; - + break; } } - // No corresponding match found in config matrix; create a new entry - config.conservativeResize (config.rows()+1, 4); - config.row(config.rows()-1) = PE.row(PE_row); - indices[PE_row] = config.rows(); - + if (indices[PE_row] == PE.rows()) { + // No corresponding match found in config matrix; create a new entry + config.conservativeResize (config.rows()+1, 4); + config.row(config.rows()-1) = PE.row(PE_row); + indices[PE_row] = config.rows(); + } } } @@ -201,7 +200,7 @@ namespace MR // Write phase-encode direction as integers; other information as floating-point out << PE.template block<1, 3>(row, 0).template cast(); if (PE.cols() > 3) - out << " " << PE.block(row, 0, 1, PE.cols()-3); + out << " " << PE.block(row, 3, 1, PE.cols()-3); out << "\n"; } } diff --git a/scripts/dwipreproc b/scripts/dwipreproc index 473865f94e..e34bc6ae0e 100755 --- a/scripts/dwipreproc +++ b/scripts/dwipreproc @@ -135,13 +135,13 @@ lib.app.gotoTempDir() dwi_size = [ int(s) for s in getHeaderInfo('dwi.mif', 'size').split() ] dwi_pe_scheme = getPEScheme('dwi.mif') if lib.app.args.topup_images: - topup_size = [ int[s] for s in getHeaderInfo('topup_in.mif', 'size').split() ] + topup_size = [ int(s) for s in getHeaderInfo('topup_in.mif', 'size').split() ] if not len(topup_size) == 4: errorMessage('File provided using -topup_images option must contain more than one image volume') topup_pe_scheme = getPEScheme('topup_in.mif') grad = getHeaderInfo('dwi.mif', 'dwgrad').split('\n') grad = [ line.split() for line in grad ] -grad = [ (float(f) for f in line) for line in grad ] +grad = [ [ float(f) for f in line ] for line in grad ] stride = getHeaderInfo('dwi.mif', 'stride') num_volumes = 1 if len(dwi_size) == 4: @@ -187,27 +187,30 @@ if manual_pe_dir: # Still construct the manual PE scheme even with 'None' or 'Pair': # there may be information in the header that we need to compare against if PE_design == 'None': - line = manual_pe_dir + line = manual_pe_dir.copy() line.append(trt) dwi_manual_pe_scheme = [ line ] * num_volumes # With 'Pair', also need to construct the manual scheme for topup_images elif PE_design == 'Pair': - dwi_manual_pe_scheme = [ manual_pe_dir.append(trt) ] * num_volumes + line = manual_pe_dir.copy() + line.append(trt) + dwi_manual_pe_scheme = [ line ] * num_volumes num_topup_volumes = topup_size[3] # Assume that first half of volumes have same direction as series; # second half have the opposite direction - topup_manual_pe_scheme = ' '.join(str(i) for i in line) * (num_topup_volumes/2) - line = [ -i for i in manual_pe_dir].extend(trt) - topup_manual_pe_scheme.append(' '.join(str(i) for i in line) * (num_topup_volumes/2)) + topup_manual_pe_scheme = [ line ] * int(num_topup_volumes/2) + line = [ (-i if i else 0.0) for i in manual_pe_dir ] + line.append(trt) + topup_manual_pe_scheme.extend( [ line ] * int(num_topup_volumes/2) ) # If -rpe_all, need to scan through grad and figure out the pairings # This will be required if relying on user-specified phase encode direction # It will also be required at the end of the script for the manual recombination elif PE_design == 'All': - grad_matchings = [ num_volumes for row in grad ] + grad_matchings = [ num_volumes ] * num_volumes grad_pairs = [ ] - for index1 in enumerate(grad): + for index1 in range(num_volumes): if grad_matchings[index1] == num_volumes: # As yet unpaired for index2 in range(index1+1, num_volumes): if grad_matchings[index2] == num_volumes: # Also as yet unpaired @@ -222,10 +225,11 @@ if manual_pe_dir: # if there's one in the header, want to compare to the manually-generated one dwi_manual_pe_scheme = [ ] for index in range(0, num_volumes): - if grad_matchings[index] > index: - dwi_manual_pe_scheme.append(manual_pe_dir.append(trt)) - else: - dwi_manual_pe_scheme.append( [ -i for i in manual_pe_dir ].append(trt)) + line = manual_pe_dir.copy() + if grad_matchings[index] < index: + line = [ (-i if i else 0.0) for i in line ] + line.append(trt) + dwi_manual_pe_scheme.append(line) else: # No manual phase encode direction defined @@ -267,7 +271,7 @@ if dwi_pe_scheme: if overwrite_scheme: dwi_pe_scheme = dwi_pe_manual_scheme # May be used later for triggering volume recombination else: - dwi_manual_pe_scheme = None # So that the scheme stored within the header will be used + dwi_manual_pe_scheme = None # To guarantee that the scheme stored within the header will be used else: # Nothing in the header; rely entirely on user specification if PE_design == 'Header': @@ -276,6 +280,27 @@ else: errorMessage('No phase encoding information provided either in header or at command-line') if auto_trt_warning: warnMessage('Total readout time not provided at command-line; assuming sane default of ' + str(auto_trt)) + dwi_pe_scheme = dwi_manual_pe_scheme # May be needed later for triggering volume recombination + + + +# This may be required by -rpe_all for extracting b=0 volumes while retaining phase-encoding information +import_dwi_pe_table_option = '' +if dwi_manual_pe_scheme: + with open('dwi_manual_pe_scheme.txt', 'w') as f: + for line in dwi_manual_pe_scheme: + f.write(' '.join( [ str(f) for f in line ] ) + '\n') + import_dwi_pe_table_option = ' -import_pe_table dwi_manual_pe_scheme.txt' + + + +# This may be required when setting up the topup call +import_topup_pe_table_option = '' +if topup_manual_pe_scheme: + with open('topup_manual_pe_scheme.txt', 'w') as f: + for line in topup_manual_pe_scheme: + f.write(' '.join( [ str(f) for f in line ] ) + '\n') + import_topup_pe_table_option = ' -import_pe_table topup_manual_pe_scheme.txt' @@ -320,12 +345,13 @@ if lib.app.args.topup_images: if not manual_trt: auto_trt_warning = True else: - if not series_pe_scheme: + if not dwi_pe_scheme: errorMessage('Unable to determine phase-encoding design of topup images') - line = series_pe_scheme[0] - topup_manual_pe_scheme = ' '.join(str(i) for i in line) * (num_topup_volumes/2) - line = [ -i for i in line[0:3]].extend(trt) - topup_manual_pe_scheme.append(' '.join(str(i) for i in line) * (num_topup_volumes/2)) + line = dwi_pe_scheme[0].copy() + topup_manual_pe_scheme = [ ' '.join(str(i) for i in line) ] * int(num_topup_volumes/2) + line = [ (-i if i else 0.0) for i in line[0:3] ] + line.append(trt) + topup_manual_pe_scheme.append( [ ' '.join(str(i) for i in line) ] * int(num_topup_volumes/2)) elif PE_design == 'All': # Criteria: @@ -354,10 +380,8 @@ elif not PE_design == 'None': # No topup images explicitly provided: In some cas # With -rpe_all, need to write inferred phase-encoding to file and import before using dwiextract so that the phase-encoding # of the extracted b=0's is propagated to the generated b=0 series if PE_design == 'All': - with open('series_manual_pe_scheme.txt', 'w') as f: - for line in series_manual_pe_scheme: - f.write(' '.join(str(i) for i in line)) - runCommand('mrconvert series.mif -import_pe_table series_manual_pe_scheme.txt - | dwiextract - topup_in.mif -bzero') + runCommand('mrconvert dwi.mif' + import_dwi_pe_table_option + ' - | dwiextract - topup_in.mif -bzero') + topup_size = [ int(s) for s in getHeaderInfo('topup_in.mif', 'size').split() ] elif PE_design == 'Header': runCommand('dwiextract dwi.mif -bzero bzeros.mif') # If there's no contrast remaining in the phase-encoding scheme, it'll be written to @@ -365,6 +389,7 @@ elif not PE_design == 'None': # No topup images explicitly provided: In some cas # In this scenario, we will be unable to run topup if getHeaderProperty('bzeros.mif', 'pe_scheme'): runCommand('mrconvert bzeros.mif topup_in.mif -stride -1,+2,+3,+4 -export_pe_table topup_datain.txt') + topup_size = [ int(s) for s in getHeaderInfo('topup_in.mif', 'size').split() ] else: warnMessage('DWI header indicates no phase encoding contrast in b=0 images; proceeding without inhomogeneity field estimation') do_topup = False @@ -372,22 +397,11 @@ elif not PE_design == 'None': # No topup images explicitly provided: In some cas - # Need gradient table if running dwi2mask after applytopup to derive a brain mask for eddy runCommand('mrinfo dwi.mif -export_grad_mrtrix grad.b') -import_dwi_pe_table_option = '' -print (dwi_manual_pe_scheme) -if dwi_manual_pe_scheme: - with open('dwi_manual_pe_scheme.txt', 'w') as f: - for line in dwi_manual_pe_scheme: - f.write(' '.join( [ str(f) for f in line ] ) + '\n') - import_dwi_pe_table_option = ' -import_pe_table dwi_manual_pe_scheme.txt' - - - eddy_in_topup_option = '' if do_topup: @@ -407,7 +421,7 @@ if do_topup: topup_in_path = 'topup_in_crop.mif' # Do the conversion in preparation for topup - runCommand('mrconvert ' + topup_in_path + ' topup_in.nii -stride -1,+2,+3,+4 -export_pe_table topup_datain.txt') + runCommand('mrconvert ' + topup_in_path + ' topup_in.nii' + import_topup_pe_table_option + ' -stride -1,+2,+3,+4 -export_pe_table topup_datain.txt') # Run topup runCommand(topup_cmd + ' --imain=topup_in.nii --datain=topup_datain.txt --out=field --fout=field_map' + fsl_suffix + ' --config=' + topup_config_path) @@ -425,9 +439,10 @@ if do_topup: index = 1 with open('applytopup_config.txt', 'r') as f: for line in f: - applytopup_index_list.append (index) image_path = 'dwi_pe_' + str(index) + '.nii' runCommand('dwiextract dwi.mif' + import_dwi_pe_table_option + ' -pe ' + ','.join(line.split()) + ' ' + image_path) + applytopup_index_list.append(str(index)) + applytopup_image_list.append(image_path) index += 1 # Finally ready to run applytopup @@ -477,11 +492,13 @@ for index1 in range(num_volumes): for index2 in range(index1+1, num_volumes): if volume_matchings[index2] == num_volumes: # Also as yet unpaired # Here, need to check both gradient matching and reversed phase-encode direction - if not max(abs(dwi_pe_scheme[index1][0:3] + dwi_pe_scheme[index2][0:3])) and grad[index1] == grad[index2]: + if not any(dwi_pe_scheme[index1][i] + dwi_pe_scheme[index2][i] for i in range(0,3)) and grad[index1] == grad[index2]: volume_matchings[index1] = index2; volume_matchings[index2] = index1; volume_pairs.append([index1, index2]) + + if not len(volume_pairs) == num_volumes/2: # Convert the resulting volume to the output image, and re-insert the diffusion encoding @@ -500,7 +517,7 @@ else: with open(bvecs_path, 'r') as f: for axis, line in enumerate(f): bvecs[axis] = line.split() - bvecs_combined_transpose = [ [] for volume in range(num_volumes) ] + bvecs_combined_transpose = [ [] * num_volumes ] for volume in range(0, num_volumes): bvec_sum = [ float(bvecs[0][volume]) + float(bvecs[0][volume+num_volumes]), float(bvecs[1][volume]) + float(bvecs[1][volume+num_volumes]), diff --git a/src/dwi/tractography/file_base.cpp b/src/dwi/tractography/file_base.cpp index 0c49401890..aafe3844dc 100644 --- a/src/dwi/tractography/file_base.cpp +++ b/src/dwi/tractography/file_base.cpp @@ -32,7 +32,7 @@ namespace MR { std::string data_file; while (kv.next()) { - std::string key = lowercase (kv.key()); + const std::string key = lowercase (kv.key()); if (key == "roi") { try { std::vector V (split (kv.value(), " \t", true, 2)); @@ -45,7 +45,7 @@ namespace MR { else if (key == "comment") properties.comments.push_back (kv.value()); else if (key == "file") data_file = kv.value(); else if (key == "datatype") dtype = DataType::parse (kv.value()); - else properties[key] = kv.value(); + else properties[kv.key()] = kv.value(); } if (dtype == DataType::Undefined) From 2da32dbb702df0a55b663c0bcaf6d5a69f618adb Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 7 Sep 2016 17:56:18 +1000 Subject: [PATCH 105/723] Scripts: runCommand(): Provide file name and line number on error --- scripts/lib/runCommand.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/lib/runCommand.py b/scripts/lib/runCommand.py index 2de4995ce8..0e4cdddea0 100644 --- a/scripts/lib/runCommand.py +++ b/scripts/lib/runCommand.py @@ -2,7 +2,7 @@ def runCommand(cmd, exitOnError=True): - import lib.app, os, subprocess, sys + import inspect, lib.app, os, subprocess, sys from lib.debugMessage import debugMessage from lib.errorMessage import errorMessage from lib.isWindows import isWindows @@ -143,8 +143,9 @@ def runCommand(cmd, exitOnError=True): if (error): if exitOnError: + caller = inspect.getframeinfo(inspect.stack()[1][0]) printMessage('') - sys.stderr.write(os.path.basename(sys.argv[0]) + ': ' + lib.app.colourError + '[ERROR] Command failed: ' + cmd + lib.app.colourClear + '\n') + sys.stderr.write(os.path.basename(sys.argv[0]) + ': ' + lib.app.colourError + '[ERROR] Command failed: ' + cmd + lib.app.colourClear + lib.app.colourDebug + ' (' + os.path.basename(caller.filename) + ':' + str(caller.lineno) + ')' + lib.app.colourClear + '\n') sys.stderr.write(os.path.basename(sys.argv[0]) + ': ' + lib.app.colourPrint + 'Output of failed command:' + lib.app.colourClear + '\n') sys.stderr.write(error_text) if not lib.app.cleanup and lib.app.tempDir: From 0498d4f2cb167cc8ed49a1e3e2ad12c028612f7a Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 8 Sep 2016 12:55:59 +1000 Subject: [PATCH 106/723] Remove uneccessary include in fod2fixel --- cmd/fod2fixel.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/fod2fixel.cpp b/cmd/fod2fixel.cpp index 75971b25f0..47eeb08ff2 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -29,7 +29,6 @@ #include "dwi/fmls.h" #include "dwi/directions/set.h" -#include "gui/dialog/progress.h" #include "file/path.h" From 670bc69a6b274f98a5f7b345f0139a0ba8bc808a Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 8 Sep 2016 14:35:23 +1000 Subject: [PATCH 107/723] amp2response: Additional testing outputs --- cmd/amp2response.cpp | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/cmd/amp2response.cpp b/cmd/amp2response.cpp index 1bd37593c7..76df5a5037 100644 --- a/cmd/amp2response.cpp +++ b/cmd/amp2response.cpp @@ -43,6 +43,7 @@ using namespace App; #define AMP2RESPONSE_DEBUG +#define AMP2RESPONSE_PERVOXEL_IMAGES @@ -144,10 +145,10 @@ void run () const size_t lmax = get_option_value ("lmax", Math::SH::LforN (volumes.size())); - auto image = header.get_image(); + auto image = header.get_image(); auto mask = Image::open (argument[1]); check_dimensions (image, mask, 0, 3); - auto dir_image = Image::open (argument[2]); + 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); @@ -168,14 +169,9 @@ void run () // Grab the image data Eigen::VectorXd data (dirs_azel.rows()); - if (volumes.size()) { - for (size_t i = 0; i != volumes.size(); ++i) { - image.index(3) = volumes[i]; - data[i] = image.value(); - } - } else { - for (image.index(3) = 0; image.index(3) != image.size(3); ++image.index(3)) - data[image.index(3)] = image.value(); + for (size_t i = 0; i != volumes.size(); ++i) { + image.index(3) = volumes[i]; + data[i] = image.value(); } // Grab the fibre direction @@ -212,6 +208,34 @@ void run () } } +#ifdef AMP2RESPONSE_PERVOXEL_IMAGES + // TODO For the sake of generating a figure, output the original and rotated signals to a dixel ODF image + Header rotated_header (header); + rotated_header.size(0) = rotated_header.size(1) = rotated_header.size(2) = 1; + rotated_header.size(3) = volumes.size(); + Header nonrotated_header (rotated_header); + nonrotated_header.size(3) = header.size(3); + Eigen::MatrixXd rotated_grad (volumes.size(), 4); + for (size_t i = 0; i != volumes.size(); ++i) { + rotated_grad.block<1,3>(i, 0) = rotated_dirs_cartesian.row(i); + rotated_grad(i, 3) = 1000.0; + } + rotated_header.set_DW_scheme (rotated_grad); + Image out_rotated = Image::create ("rotated_amps_" + str(sf_counter) + ".mif", rotated_header); + Image out_nonrotated = Image::create ("nonrotated_amps_" + str(sf_counter) + ".mif", nonrotated_header); + out_rotated.index(0) = out_rotated.index(1) = out_rotated.index(2) = 0; + out_nonrotated.index(0) = out_nonrotated.index(1) = out_nonrotated.index(2) = 0; + for (size_t i = 0; i != volumes.size(); ++i) { + image.index(3) = volumes[i]; + out_rotated.index(3) = i; + out_rotated.value() = image.value(); + } + for (size_t i = 0; i != header.size(3); ++i) { + image.index(3) = out_nonrotated.index(3) = i; + out_nonrotated.value() = image.value(); + } +#endif + // Generate the ZSH -> amplitude transform Eigen::MatrixXd transform = Math::ZSH::init_amp_transform (rotated_dirs_azel.col(1), lmax); From e85208a5393186bd083e9989dce6060ec12916cf Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 8 Sep 2016 14:48:23 +1000 Subject: [PATCH 108/723] fix issue with fod2fixel output --- cmd/fod2fixel.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cmd/fod2fixel.cpp b/cmd/fod2fixel.cpp index 47eeb08ff2..ff4fbb6bde 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -339,17 +339,13 @@ void run () FixelFormat::check_fixel_folder (fixel_folder_path, true, true); - std::unique_ptr writer (new FMLS::FODQueueWriter(fod_data, mask)); + FMLS::FODQueueWriter writer (fod_data, mask); const DWI::Directions::Set dirs (1281); - std::unique_ptr fmls (new Segmenter (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); - - writer.release (); - fmls.release (); + 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 (); } From a74f3ea0953a362d7f86176c2ef2a1a4481505e5 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 8 Sep 2016 16:36:37 +1000 Subject: [PATCH 109/723] mrcat: Fix concatenation of phase encoding schemes --- cmd/mrcat.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/mrcat.cpp b/cmd/mrcat.cpp index b28d0c7484..50ae51bb0d 100644 --- a/cmd/mrcat.cpp +++ b/cmd/mrcat.cpp @@ -168,8 +168,8 @@ void run () { scheme_out.resize (nrows, ncols); size_t row = 0; for (int n = 0; n != num_images; ++n) { - for (ssize_t i = 0; i != input_grads[n].rows(); ++i, ++row) - scheme_out.row(row) = input_grads[n].row(i); + for (ssize_t i = 0; i != input_schemes[n].rows(); ++i, ++row) + scheme_out.row(row) = input_schemes[n].row(i); } } PhaseEncoding::set_scheme (header_out, scheme_out); From d23bacf458ef3af729cb8063aea521bd643e805a Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 8 Sep 2016 16:37:42 +1000 Subject: [PATCH 110/723] JSON import: Better type conversion and catch parsing errors --- lib/file/json_utils.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/file/json_utils.cpp b/lib/file/json_utils.cpp index fa190963a6..0cc5d94576 100644 --- a/lib/file/json_utils.cpp +++ b/lib/file/json_utils.cpp @@ -39,13 +39,27 @@ namespace MR nlohmann::json json; try { in >> json; - } catch (...) { - throw Exception ("Error parsing JSON file \"" + path + "\""); + } catch (std::logic_error& e) { + throw Exception ("Error parsing JSON file \"" + path + "\": " + e.what()); } for (auto i = json.cbegin(); i != json.cend(); ++i) { - // Only load simple parameters at the first level - if (i->is_primitive()) - H.keyval().insert (std::make_pair (i.key(), str(i.value()))); + + if (i->is_boolean()) { + H.keyval().insert (std::make_pair (i.key(), i.value() ? "1" : "0")); + } else if (i->is_number_integer()) { + H.keyval().insert (std::make_pair (i.key(), str(i.value()))); + } else if (i->is_number_float()) { + H.keyval().insert (std::make_pair (i.key(), str(i.value()))); + } else if (i->is_array()) { + std::vector s; + for (auto j = i->cbegin(); j != i->cend(); ++j) + s.push_back (str(*j)); + H.keyval().insert (std::make_pair (i.key(), join(s, "\n"))); + } else if (i->is_string()) { + const std::string s = i.value(); + H.keyval().insert (std::make_pair (i.key(), s)); + } + } } From fdc287cb04304889d856e62edddf5bda0e1d6da8 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 8 Sep 2016 16:38:32 +1000 Subject: [PATCH 111/723] dwipreproc: Fix generation of text files for applytopup --- scripts/dwipreproc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/dwipreproc b/scripts/dwipreproc index e34bc6ae0e..79f3c3f997 100755 --- a/scripts/dwipreproc +++ b/scripts/dwipreproc @@ -432,7 +432,7 @@ if do_topup: if dwi_manual_pe_scheme: runCommand('mrconvert dwi.mif' + import_dwi_pe_table_option + ' - | mrinfo - -export_pe_eddy applytopup_config.txt applytopup_indices.txt') else: - runCommand('mrinfo ' + topup_in_path + ' -export_pe_eddy applytopup_config.txt applytopup_indices.txt') + runCommand('mrinfo dwi.mif -export_pe_eddy applytopup_config.txt applytopup_indices.txt') applytopup_index_list = [ ] applytopup_image_list = [ ] @@ -513,6 +513,8 @@ else: # Particularly if eddy has provided rotated bvecs, since we're combining two volumes into one that # potentially have subject rotation between them (and therefore the sensitisation direction is # not precisely equivalent), the best we can do is take the mean of the two vectors. + # TODO Re-test LSR + # TODO Manual recombination of volumes needs to take into account the explicit volume matching bvecs = [ [] for axis in range(3) ] with open(bvecs_path, 'r') as f: for axis, line in enumerate(f): From 012b2c736588cbbe1838a423d2c78488a22d541a Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Fri, 9 Sep 2016 13:26:28 +1000 Subject: [PATCH 112/723] fix input path error in fixelcfestats --- cmd/fixelcfestats.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 84efa98d12..20a199c9dc 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -298,7 +298,7 @@ void run() { for (size_t subject = 0; subject < identifiers.size(); subject++) { LogLevelLatch log_level (0); - auto subject_data = Image::open (Path::join(input_fixel_folder, identifiers[subject])).with_direct_io(); + auto subject_data = Image::open (identifiers[subject]).with_direct_io(); std::vector subject_data_vector (num_fixels, 0.0); for (auto i = Loop (index_image)(index_image); i; ++i) { index_image.index(3) = 1; From 1c78f914ec9f147b16714603b9dac630fdec7cff Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Fri, 9 Sep 2016 14:40:05 +1000 Subject: [PATCH 113/723] Added command documentation for ported fixel commands --- docs/reference/commands/fixelcrop.rst | 57 +++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 docs/reference/commands/fixelcrop.rst diff --git a/docs/reference/commands/fixelcrop.rst b/docs/reference/commands/fixelcrop.rst new file mode 100644 index 0000000000..1f4b27e196 --- /dev/null +++ b/docs/reference/commands/fixelcrop.rst @@ -0,0 +1,57 @@ +.. _fixelcrop: + +fixelcrop +=========== + +Synopsis +-------- + +:: + + fixelcrop [ options ] input_fixel_folder input_fixel_data_mask output_fixel_folder + +- *input_fixel_folder*: the input fixel folder file to be cropped +- *input_fixel_data_mask*: the input fixel data file to be cropped +- *output_fixel_folder*: the output fixel folder + +Description +----------- + +Crop a fixel index image (i.e. remove fixels) using a fixel mask + +Options +------- + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) + +- **-failonwarn** terminate program if a warning is produced + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +-------------- + + + +**Author:** David Raffelt (david.raffelt@florey.edu.au) & Rami Tabarra (rami.tabarra@florey.edu.au) + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + From 9df1b224dca51492172148f2dc5ab37985db210d Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Fri, 9 Sep 2016 14:40:29 +1000 Subject: [PATCH 114/723] Added command documentation for ported fixel commands --- docs/reference/commands/dwidenoise.rst | 4 +++- docs/reference/commands/fixel2sh.rst | 8 +++++--- docs/reference/commands/fixel2tsf.rst | 6 +++--- docs/reference/commands/fixel2voxel.rst | 10 +++++----- docs/reference/commands/fixelcfestats.rst | 12 +++++------ .../commands/fixelcorrespondence.rst | 13 ++++++------ docs/reference/commands/fixelreorient.rst | 8 ++++---- docs/reference/commands/fod2fixel.rst | 13 ++++++++---- docs/reference/commands_list.rst | 20 +++++++++---------- docs/reference/scripts/dwi2response.rst | 4 +++- 10 files changed, 54 insertions(+), 44 deletions(-) diff --git a/docs/reference/commands/dwidenoise.rst b/docs/reference/commands/dwidenoise.rst index fef943fb34..ecc9e671b9 100644 --- a/docs/reference/commands/dwidenoise.rst +++ b/docs/reference/commands/dwidenoise.rst @@ -55,7 +55,9 @@ Standard options References ^^^^^^^^^^ -Veraart, J.; Fieremans, E. & Novikov, D.S. Diffusion MRI noise mapping using random matrix theory Magn. Res. Med., 2016, early view, doi: 10.1002/mrm.26059 +Veraart, J.; Novikov, D.S.; Christiaens, D.; Ades-aron, B.; Sijbers, J. & Fieremans, E. Denoising of diffusion MRI using random matrix theory. NeuroImage, 2016, in press, doi: 10.1016/j.neuroimage.2016.08.016 + +Veraart, J.; Fieremans, E. & Novikov, D.S. Diffusion MRI noise mapping using random matrix theory. Magn. Res. Med., 2016, early view, doi: 10.1002/mrm.26059 -------------- diff --git a/docs/reference/commands/fixel2sh.rst b/docs/reference/commands/fixel2sh.rst index f1981a4e8b..aa3cd9ddbb 100644 --- a/docs/reference/commands/fixel2sh.rst +++ b/docs/reference/commands/fixel2sh.rst @@ -10,17 +10,19 @@ Synopsis fixel2sh [ options ] fixel_in sh_out -- *fixel_in*: the input sparse fixel image. +- *fixel_in*: the input fixel data file. - *sh_out*: the output sh image. Description ----------- -convert a fixel-based sparse-data image into an SH image that can be visually evaluated using MRview +convert a fixel-based sparse-data image into an spherical harmonic image 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. Options ------- +- **-lmax order** set the maximum harmonic order for the output series (Default: 8) + Standard options ^^^^^^^^^^^^^^^^ @@ -44,7 +46,7 @@ Standard options -**Author:** Robert E. Smith (robert.smith@florey.edu.au) +**Author:** Robert E. Smith (robert.smith@florey.edu.au) & David Raffelt (david.raffelt@florey.edu.au) **Copyright:** Copyright (c) 2008-2016 the MRtrix3 contributors diff --git a/docs/reference/commands/fixel2tsf.rst b/docs/reference/commands/fixel2tsf.rst index 6dfdd01a2d..82893e2c12 100644 --- a/docs/reference/commands/fixel2tsf.rst +++ b/docs/reference/commands/fixel2tsf.rst @@ -10,19 +10,19 @@ Synopsis fixel2tsf [ options ] fixel_in tracks tsf -- *fixel_in*: the input fixel image +- *fixel_in*: the input fixel data file (within the fixel folder) - *tracks*: the input track file - *tsf*: the output track scalar file Description ----------- -Map fixel values to a track scalar file based on an input tractogram. This is useful for visualising the output from fixelcfestats in 3D. +Map fixel values to a track scalar file based on an input tractogram. This is useful for visualising all brain fixels (e.g. the output from fixelcfestats) in 3D. Options ------- -- **-angle value** the max anglular threshold for computing correspondence between a fixel direction and track tangent (default = 30 degrees) +- **-angle value** the max anglular threshold for computing correspondence between a fixel direction and track tangent (default = 45 degrees) Standard options ^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/fixel2voxel.rst b/docs/reference/commands/fixel2voxel.rst index 73b7bbb506..5c8e721056 100644 --- a/docs/reference/commands/fixel2voxel.rst +++ b/docs/reference/commands/fixel2voxel.rst @@ -10,19 +10,19 @@ Synopsis fixel2voxel [ options ] fixel_in operation image_out -- *fixel_in*: the input sparse fixel image. -- *operation*: the operation to apply, one of: mean, sum, product, rms, var, std, min, max, absmax, magmax, count, complexity, sf, dec_unit, dec_scaled, split_size, split_value, split_dir. +- *fixel_in*: the input fixel data file +- *operation*: the operation to apply, one of: mean, sum, product, min, max, absmax, magmax, count, complexity, sf, dec_unit, dec_scaled, split_data, split_dir. - *image_out*: the output scalar image. Description ----------- -convert a fixel-based sparse-data image into some form of scalar image. This could be: - Some statistic computed across all fixel values within a voxel: mean, sum, product, rms, var, std, 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 scalar image with one 3D volume per fixel: split_size, split_value- A 4D image with three 3D volumes per fixel direction: split_dir +convert a fixel-based sparse-data image into some form of scalar image. This could be: - 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 scalar image of fixel values with one 3D volume per fixel: split_data- A 4D image of fixel directions, stored as three 3D volumes per fixel direction: split_dir Options ------- -- **-weighted** weight the contribution of each fixel to the per-voxel result according to its volume (note that this option is not applicable for all operations, and should be avoided if the value stored in the fixel image is itself the estimated fibre volume) +- **-weighted fixel_in** weight the contribution of each fixel to the per-voxel result according to its volume. E.g. when estimating a voxel-based measure of mean axon diameter, a fixel's mean axon diameter should be weigthed by its relative volume within the voxel. Note that AFD can be used as a psuedomeasure of fixel volume. Standard options ^^^^^^^^^^^^^^^^ @@ -52,7 +52,7 @@ References -**Author:** Robert E. Smith (robert.smith@florey.edu.au) +**Author:** Robert E. Smith (robert.smith@florey.edu.au) & David Raffelt (david.raffelt@florey.edu.au) **Copyright:** Copyright (c) 2008-2016 the MRtrix3 contributors diff --git a/docs/reference/commands/fixelcfestats.rst b/docs/reference/commands/fixelcfestats.rst index 32e4ef2346..7bef303a97 100644 --- a/docs/reference/commands/fixelcfestats.rst +++ b/docs/reference/commands/fixelcfestats.rst @@ -8,14 +8,14 @@ Synopsis :: - fixelcfestats [ options ] input template design contrast tracks output + fixelcfestats [ options ] in_fixel_folder subjects design contrast tracks out_fixel_folder -- *input*: a text file listing the file names of the input fixel images -- *template*: the fixel mask used to define fixels of interest. This can be generated by thresholding the group average AFD fixel image. +- *in_fixel_folder*: the fixel folder containing the data files for each subject (after obtaining fixel correspondence +- *subjects*: a text file listing the subject identifiers (one per line). This should correspond with the filenames in the fixel folder (including the file extension), and be listed in the same order as the rows of the design matrix. - *design*: the design matrix. Note that a column of 1's will need to be added for correlations. - *contrast*: the contrast vector, specified as a single row of weights - *tracks*: the tracks used to determine fixel-fixel connectivity -- *output*: the filename prefix for all output. +- *out_fixel_folder*: the output folder where results will be saved. Will be created if it does not exist Description ----------- @@ -52,11 +52,11 @@ Additional options for fixelcfestats - **-negative** automatically test the negative (opposite) contrast. By computing the opposite contrast simultaneously the computation time is reduced. -- **-angle value** the max angle threshold for computing inter-subject fixel correspondence (Default: 30 degrees) +- **-smooth FWHM** smooth the fixel value along the fibre tracts using a Gaussian kernel with the supplied FWHM (default: 10mm) - **-connectivity threshold** a threshold to define the required fraction of shared connections to be included in the neighbourhood (default: 0.01) -- **-smooth FWHM** smooth the fixel value along the fibre tracts using a Gaussian kernel with the supplied FWHM (default: 10mm) +- **-angle value** the max angle threshold for assigning streamline tangents to fixels (Default: 45 degrees) Standard options ^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/fixelcorrespondence.rst b/docs/reference/commands/fixelcorrespondence.rst index e79a3f04a4..7b4f9c5912 100644 --- a/docs/reference/commands/fixelcorrespondence.rst +++ b/docs/reference/commands/fixelcorrespondence.rst @@ -8,21 +8,22 @@ Synopsis :: - fixelcorrespondence [ options ] subject template output + fixelcorrespondence [ options ] subject_data template_folder output_folder output_data -- *subject*: the input subject fixel image. -- *template*: the input template fixel image. -- *output*: the output fixel image. +- *subject_data*: the input subject fixel data file. This should be a file inside the fixel folder +- *template_folder*: the input template fixel folder. +- *output_folder*: the output fixel folder. Any existing index file in the output folder will be checked that it matches the expected output. +- *output_data*: the name of the output fixel data file. This will be placed in the output fixel folder Description ----------- -Obtain angular correpondence by mapping subject fixels to a template fixel mask. It is assumed that the subject image has already been spatially normalised and is aligned with the template. +Obtain fixel-fixel correpondence between a subject fixel image and a template fixel mask.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. Options ------- -- **-angle value** the max angle threshold for computing inter-subject fixel correspondence (Default: 30 degrees) +- **-angle value** the max angle threshold for computing inter-subject fixel correspondence (Default: 45 degrees) Standard options ^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/fixelreorient.rst b/docs/reference/commands/fixelreorient.rst index 698260591f..d6aadcc580 100644 --- a/docs/reference/commands/fixelreorient.rst +++ b/docs/reference/commands/fixelreorient.rst @@ -8,16 +8,16 @@ Synopsis :: - fixelreorient [ options ] input warp output + fixelreorient [ options ] fixel_in warp fixel_out -- *input*: the input fixel image. +- *fixel_in*: the fixel folder - *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 -- *output*: the output fixel image. +- *fixel_out*: the output fixel folder. If the the input and output folders are the same, the existing directions file will be replaced (providing the --force option is supplied). If a new folder is supplied then all fixel data will be copied to the new folder. Description ----------- -Reorient fixel directions using the local affine transformation (Jacobian matrix) of an input warp. +Reorient fixel directions. 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. Options ------- diff --git a/docs/reference/commands/fod2fixel.rst b/docs/reference/commands/fod2fixel.rst index a9b3bf83f3..f3f9eecba0 100644 --- a/docs/reference/commands/fod2fixel.rst +++ b/docs/reference/commands/fod2fixel.rst @@ -8,9 +8,10 @@ Synopsis :: - fod2fixel [ options ] fod + fod2fixel [ options ] fod fixel_folder - *fod*: the input fod image. +- *fixel_folder*: the output fixel folder Description ----------- @@ -25,11 +26,11 @@ Options Metric values for fixel-based sparse output images ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- **-afd image** store the total Apparent Fibre Density per fixel (integral of FOD lobe) +- **-afd image** output the total Apparent Fibre Density per fixel (integral of FOD lobe) -- **-peak image** store the peak FOD amplitude per fixel +- **-peak image** output the peak FOD amplitude per fixel -- **-disp image** store a measure of dispersion per fixel as the ratio between FOD lobe integral and peak amplitude +- **-disp image** output a measure of dispersion per fixel as the ratio between FOD lobe integral and peak amplitude FOD FMLS segmenter options ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -44,6 +45,10 @@ FOD FMLS segmenter options - **-fmls_peak_ratio_to_merge value** specify the amplitude ratio between a sample and the smallest peak amplitude of the adjoining lobes, above which the lobes will be merged. This is the relative amplitude between the smallest of two adjoining lobes, and the 'bridge' between the two lobes. A value of 1.0 will never merge two peaks into a single lobe; a value of 0.0 will always merge lobes unless they are bisected by a zero crossing. Default: 1. +- **-nii** output the directions and index file in nii format (instead of the default mif) + +- **-dirpeak** define the fixel direction as the peak lobe direction as opposed to the lobe mean + Standard options ^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index 6a26a102e6..00bfc2aaed 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -9,6 +9,8 @@ List of MRtrix3 commands + commands/3ttnormalise + commands/5tt2gmwmi commands/5tt2vis @@ -61,22 +63,16 @@ List of MRtrix3 commands commands/fixel2voxel - commands/fixelcalc - commands/fixelcfestats - commands/fixelcorrespondence + commands/fixelconvert - commands/fixelhistogram + commands/fixelcorrespondence - commands/fixellog + commands/fixelcrop commands/fixelreorient - commands/fixelstats - - commands/fixelthreshold - commands/fod2dec commands/fod2fixel @@ -123,6 +119,8 @@ List of MRtrix3 commands commands/mrmetric + commands/mrmodelfield + commands/mrpad commands/mrregister @@ -173,10 +171,10 @@ List of MRtrix3 commands commands/tcksample - commands/tcksift - commands/tcksift2 + commands/tcksift + commands/tckstats commands/tensor2metric diff --git a/docs/reference/scripts/dwi2response.rst b/docs/reference/scripts/dwi2response.rst index a19ffc132c..66b20930d9 100644 --- a/docs/reference/scripts/dwi2response.rst +++ b/docs/reference/scripts/dwi2response.rst @@ -68,7 +68,9 @@ Standard options - **-quiet** Suppress all console output during script execution -- **-verbose** Display additional information for every command invoked +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output References ^^^^^^^^^^ From 8605d537b565576c4b0a444652eb04bcd394b1f8 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Fri, 9 Sep 2016 14:41:45 +1000 Subject: [PATCH 115/723] Removed old fixel command documentation --- docs/reference/commands/fixelcalc.rst | 58 ------------------- docs/reference/commands/fixelhistogram.rst | 66 ---------------------- docs/reference/commands/fixellog.rst | 56 ------------------ docs/reference/commands/fixelstats.rst | 64 --------------------- docs/reference/commands/fixelthreshold.rst | 61 -------------------- 5 files changed, 305 deletions(-) delete mode 100644 docs/reference/commands/fixelcalc.rst delete mode 100644 docs/reference/commands/fixelhistogram.rst delete mode 100644 docs/reference/commands/fixellog.rst delete mode 100644 docs/reference/commands/fixelstats.rst delete mode 100644 docs/reference/commands/fixelthreshold.rst diff --git a/docs/reference/commands/fixelcalc.rst b/docs/reference/commands/fixelcalc.rst deleted file mode 100644 index 0ea5849266..0000000000 --- a/docs/reference/commands/fixelcalc.rst +++ /dev/null @@ -1,58 +0,0 @@ -.. _fixelcalc: - -fixelcalc -=========== - -Synopsis --------- - -:: - - fixelcalc [ options ] input1 operation input2 output - -- *input1*: the input fixel image. -- *operation*: the type of operation to be applied (either add, sub, mult or divide) -- *input2*: the input fixel image. -- *output*: the output fixel image. - -Description ------------ - -Perform basic calculations (add, subtract, multiply, divide) between two fixel images - -Options -------- - -Standard options -^^^^^^^^^^^^^^^^ - -- **-info** display information messages. - -- **-quiet** do not display information messages or progress status. - -- **-debug** display debugging messages. - -- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. - -- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) - -- **-failonwarn** terminate program if a warning is produced - -- **-help** display this information page and exit. - -- **-version** display version information and exit. - --------------- - - - -**Author:** David Raffelt (david.raffelt@florey.edu.au) - -**Copyright:** Copyright (c) 2008-2016 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/ - -MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -For more details, see www.mrtrix.org - diff --git a/docs/reference/commands/fixelhistogram.rst b/docs/reference/commands/fixelhistogram.rst deleted file mode 100644 index 229816a03e..0000000000 --- a/docs/reference/commands/fixelhistogram.rst +++ /dev/null @@ -1,66 +0,0 @@ -.. _fixelhistogram: - -fixelhistogram -=========== - -Synopsis --------- - -:: - - fixelhistogram [ options ] input - -- *input*: the input fixel image. - -Description ------------ - -Generate a histogram of fixel values. - -Options -------- - -Histogram generation options -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -- **-bins num** Manually set the number of bins to use to generate the histogram. - -- **-template file** Use an existing histogram file as the template for histogram formation - -- **-mask image** Calculate the histogram only within a mask image. - -- **-ignorezero** ignore zero-valued data during histogram construction. - -Standard options -^^^^^^^^^^^^^^^^ - -- **-info** display information messages. - -- **-quiet** do not display information messages or progress status. - -- **-debug** display debugging messages. - -- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. - -- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) - -- **-failonwarn** terminate program if a warning is produced - -- **-help** display this information page and exit. - -- **-version** display version information and exit. - --------------- - - - -**Author:** Robert E. Smith (robert.smith@florey.edu.au) - -**Copyright:** Copyright (c) 2008-2016 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/ - -MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -For more details, see www.mrtrix.org - diff --git a/docs/reference/commands/fixellog.rst b/docs/reference/commands/fixellog.rst deleted file mode 100644 index 3f7d3b23b6..0000000000 --- a/docs/reference/commands/fixellog.rst +++ /dev/null @@ -1,56 +0,0 @@ -.. _fixellog: - -fixellog -=========== - -Synopsis --------- - -:: - - fixellog [ options ] input output - -- *input*: the input fixel image. -- *output*: the output fixel image. - -Description ------------ - -compute the natural logarithm of all values in a fixel image - -Options -------- - -Standard options -^^^^^^^^^^^^^^^^ - -- **-info** display information messages. - -- **-quiet** do not display information messages or progress status. - -- **-debug** display debugging messages. - -- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. - -- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) - -- **-failonwarn** terminate program if a warning is produced - -- **-help** display this information page and exit. - -- **-version** display version information and exit. - --------------- - - - -**Author:** David Raffelt (david.raffelt@florey.edu.au) - -**Copyright:** Copyright (c) 2008-2016 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/ - -MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -For more details, see www.mrtrix.org - diff --git a/docs/reference/commands/fixelstats.rst b/docs/reference/commands/fixelstats.rst deleted file mode 100644 index 7a5cf0e777..0000000000 --- a/docs/reference/commands/fixelstats.rst +++ /dev/null @@ -1,64 +0,0 @@ -.. _fixelstats: - -fixelstats -=========== - -Synopsis --------- - -:: - - fixelstats [ options ] input - -- *input*: the input fixel image. - -Description ------------ - -Compute fixel image statistics - -Options -------- - -Statistics options -^^^^^^^^^^^^^^^^^^ - -- **-output field** output only the field specified. Multiple such options can be supplied if required. Choices are: mean, median, std, min, max, count. Useful for use in scripts. - -- **-mask image** only perform computation within the specified binary mask image. - -- **-ignorezero** ignore zero values during statistics calculation - -Standard options -^^^^^^^^^^^^^^^^ - -- **-info** display information messages. - -- **-quiet** do not display information messages or progress status. - -- **-debug** display debugging messages. - -- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. - -- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) - -- **-failonwarn** terminate program if a warning is produced - -- **-help** display this information page and exit. - -- **-version** display version information and exit. - --------------- - - - -**Author:** David Raffelt (david.raffelt@florey.edu.au) - -**Copyright:** Copyright (c) 2008-2016 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/ - -MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -For more details, see www.mrtrix.org - diff --git a/docs/reference/commands/fixelthreshold.rst b/docs/reference/commands/fixelthreshold.rst deleted file mode 100644 index 020b1a8345..0000000000 --- a/docs/reference/commands/fixelthreshold.rst +++ /dev/null @@ -1,61 +0,0 @@ -.. _fixelthreshold: - -fixelthreshold -=========== - -Synopsis --------- - -:: - - fixelthreshold [ options ] fixel_in threshold fixel_out - -- *fixel_in*: the input fixel image. -- *threshold*: the input threshold -- *fixel_out*: the output fixel image - -Description ------------ - -Threshold the values in a fixel image - -Options -------- - -- **-crop** remove fixels that fall below threshold (instead of assigning their value to zero or one) - -- **-invert** invert the output image (i.e. below threshold fixels are included instead) - -Standard options -^^^^^^^^^^^^^^^^ - -- **-info** display information messages. - -- **-quiet** do not display information messages or progress status. - -- **-debug** display debugging messages. - -- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. - -- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) - -- **-failonwarn** terminate program if a warning is produced - -- **-help** display this information page and exit. - -- **-version** display version information and exit. - --------------- - - - -**Author:** David Raffelt (david.raffelt@florey.edu.au) - -**Copyright:** Copyright (c) 2008-2016 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/ - -MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -For more details, see www.mrtrix.org - From 36636a82ce308e0de6b2a1398513985fc7f78e13 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Fri, 9 Sep 2016 14:44:22 +1000 Subject: [PATCH 116/723] Added documentation for new biasfield correction and intensity normalisation --- cmd/fixel2tsf.cpp | 2 +- cmd/fixelcfestats.cpp | 2 + cmd/fod2fixel.cpp | 66 ++++++++++++------------ docs/reference/commands/3ttnormalise.rst | 62 ++++++++++++++++++++++ docs/reference/commands/mrmodelfield.rst | 58 +++++++++++++++++++++ docs/workflows/fixel_based_analysis.rst | 6 +-- 6 files changed, 159 insertions(+), 37 deletions(-) create mode 100644 docs/reference/commands/3ttnormalise.rst create mode 100644 docs/reference/commands/mrmodelfield.rst diff --git a/cmd/fixel2tsf.cpp b/cmd/fixel2tsf.cpp index 1b7793fe8f..468a59e76e 100644 --- a/cmd/fixel2tsf.cpp +++ b/cmd/fixel2tsf.cpp @@ -47,7 +47,7 @@ void usage () "This is useful for visualising all brain fixels (e.g. the output from fixelcfestats) in 3D."; ARGUMENTS - + Argument ("fixel_in", "the input fixel data file").type_image_in () + + Argument ("fixel_in", "the input fixel data file (within the fixel folder)").type_image_in () + Argument ("tracks", "the input track file ").type_tracks_in () + Argument ("tsf", "the output track scalar file").type_file_out (); diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 20a199c9dc..add6b18e72 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -315,6 +315,8 @@ void run() { for (; it != smoothing_weights[fixel].end(); ++it) value += subject_data_vector[it->first] * it->second.value; data (fixel, subject) = value; + if (!std::isfinite(value)) + std::cout << value << std::endl; } progress++; } diff --git a/cmd/fod2fixel.cpp b/cmd/fod2fixel.cpp index ff4fbb6bde..d98e920d9c 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -202,48 +202,48 @@ void Segmented_FOD_receiver::commit () auto index_header (H); index_header.keyval()[FixelFormat::n_fixels_key] = str(n_fixels); - index_header.ndim () = 4; - index_header.size (3) = 2; - index_header.datatype () = DataType::from (); - index_header.datatype ().set_byte_order_native (); + index_header.ndim() = 4; + index_header.size(3) = 2; + index_header.datatype() = DataType::from(); + index_header.datatype().set_byte_order_native(); index_image = std::unique_ptr (new IndexImage (IndexImage::create (index_filepath, index_header))); auto fixel_data_header (H); - fixel_data_header.ndim () = 3; - fixel_data_header.size (0) = n_fixels; - fixel_data_header.size (2) = 1; - fixel_data_header.datatype () = DataType::Float32; - fixel_data_header.datatype ().set_byte_order_native(); + fixel_data_header.ndim() = 3; + fixel_data_header.size(0) = n_fixels; + fixel_data_header.size(2) = 1; + fixel_data_header.datatype() = DataType::Float32; + fixel_data_header.datatype().set_byte_order_native(); - if (dir_path.size ()) { + if (dir_path.size()) { auto dir_header (fixel_data_header); - dir_header.size (1) = 3; + dir_header.size(1) = 3; dir_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_folder_path, dir_path), dir_header))); - dir_image->index (1) = 0; + dir_image->index(1) = 0; FixelFormat::check_fixel_size (*index_image, *dir_image); } - if (afd_path.size ()) { + if (afd_path.size()) { auto afd_header (fixel_data_header); - afd_header.size (1) = 1; + afd_header.size(1) = 1; afd_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_folder_path, afd_path), afd_header))); - afd_image->index (1) = 0; + afd_image->index(1) = 0; FixelFormat::check_fixel_size (*index_image, *afd_image); } - if (peak_path.size ()) { - auto peak_header (fixel_data_header); - peak_header.size (1) = 1; + if (peak_path.size()) { + auto peak_header(fixel_data_header); + peak_header.size(1) = 1; peak_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_folder_path, peak_path), peak_header))); - peak_image->index (1) = 0; + peak_image->index(1) = 0; FixelFormat::check_fixel_size (*index_image, *peak_image); } - if (disp_path.size ()) { + if (disp_path.size()) { auto disp_header (fixel_data_header); - disp_header.size (1) = 1; + disp_header.size(1) = 1; disp_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_folder_path, disp_path), disp_header))); - disp_image->index (1) = 0; + disp_image->index(1) = 0; FixelFormat::check_fixel_size (*index_image, *disp_image); } @@ -255,37 +255,37 @@ void Segmented_FOD_receiver::commit () assign_pos_of (vox_fixels.vox).to (*index_image); - index_image->index (3) = 0; + index_image->index(3) = 0; index_image->value () = n_vox_fixels; - index_image->index (3) = 1; - index_image->value () = offset; + index_image->index(3) = 1; + index_image->value() = offset; if (dir_image) { for (size_t i = 0; i < n_vox_fixels; ++i) { - dir_image->index (0) = offset + i; - dir_image->row (1) = vox_fixels[i].dir; + dir_image->index(0) = offset + i; + dir_image->row(1) = vox_fixels[i].dir; } } if (afd_image) { for (size_t i = 0; i < n_vox_fixels; ++i) { - afd_image->index (0) = offset + i; - afd_image->value () = vox_fixels[i].integral; + afd_image->index(0) = offset + i; + afd_image->value() = vox_fixels[i].integral; } } if (peak_image) { for (size_t i = 0; i < n_vox_fixels; ++i) { - peak_image->index (0) = offset + i; - peak_image->value () = vox_fixels[i].peak_value; + peak_image->index(0) = offset + i; + peak_image->value() = vox_fixels[i].peak_value; } } if (disp_image) { for (size_t i = 0; i < n_vox_fixels; ++i) { - disp_image->index (0) = offset + i; - disp_image->value () = vox_fixels[i].integral / vox_fixels[i].peak_value; + disp_image->index(0) = offset + i; + disp_image->value() = vox_fixels[i].integral / vox_fixels[i].peak_value; } } diff --git a/docs/reference/commands/3ttnormalise.rst b/docs/reference/commands/3ttnormalise.rst new file mode 100644 index 0000000000..a1a901df47 --- /dev/null +++ b/docs/reference/commands/3ttnormalise.rst @@ -0,0 +1,62 @@ +.. _3ttnormalise: + +3ttnormalise +=========== + +Synopsis +-------- + +:: + + 3ttnormalise [ options ] wm gm csf wm_out gm_out csf_out + +- *wm*: the white matter compartment (FOD) image +- *gm*: the grey matter compartment image +- *csf*: the cerebral spinal fluid comparment image +- *wm_out*: the output white matter compartment (FOD) image +- *gm_out*: the output grey matter compartment image +- *csf_out*: the output cerebral spinal fluid comparment image + +Description +----------- + +Globally normalise three tissue (wm, gm, csf) comparments from multi-tissue CSD such that their sum within each voxel is as close to 1 as possible. This involves solving for a single scale factor for each compartment map. + +Options +------- + +- **-mask image** define the mask to compute the normalisation within. If not supplied this is estimated automatically + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) + +- **-failonwarn** terminate program if a warning is produced + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +-------------- + + + +**Author:** David Raffelt (david.raffelt@florey.edu.au) + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + diff --git a/docs/reference/commands/mrmodelfield.rst b/docs/reference/commands/mrmodelfield.rst new file mode 100644 index 0000000000..1edd01593a --- /dev/null +++ b/docs/reference/commands/mrmodelfield.rst @@ -0,0 +1,58 @@ +.. _mrmodelfield: + +mrmodelfield +=========== + +Synopsis +-------- + +:: + + mrmodelfield [ options ] input output + +- *input*: the input image +- *output*: the output image representing the fit + +Description +----------- + +Model an input image using low frequency 3D polynomial basis functions. This command was designed to estimate a DWI bias field using the sum of normalised multi-tissue CSD compartments. + +Options +------- + +- **-mask image** use only voxels within the supplied mask for the model fit. If not supplied this command will compute a mask + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) + +- **-failonwarn** terminate program if a warning is produced + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +-------------- + + + +**Author:** David Raffelt (david.raffelt@florey.edu.au) & Rami Tabbara (rami.tabbara@florey.edu.au) + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + diff --git a/docs/workflows/fixel_based_analysis.rst b/docs/workflows/fixel_based_analysis.rst index e9ee15a055..950e756a1f 100644 --- a/docs/workflows/fixel_based_analysis.rst +++ b/docs/workflows/fixel_based_analysis.rst @@ -98,16 +98,16 @@ Note that here we transform FOD images into template space *without* FOD reorien ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Here we segment each FOD lobe to identify the number and orientation of fixels in each voxel. The output also contains the apparent fibre density (AFD) value per fixel estimated as the FOD lobe integral (see `here `_ for details on FOD segmentation). Note that in the following steps we will use a more generic shortened acronym - Fibre Density (FD) instead of AFD for consistency with our recent work (paper under review):: - fod2fixel -mask -afd + fod2fixel -mask .. NOTE:: If you would like to perform fixel-based analysis of metrics derived from other diffusion MRI models (e.g. CHARMED), replace steps 8 & 9. For example, in step 8 you can warp preprocessed DW images (also without any reorientation). In step 9 you could then estimate your DWI model of choice. 10. Reorient fixel orientations ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Here we reorient the direction of all fixels based on the Jacobian matrix (local affine transformation) at each voxel in the warp:: +Here we reorient the direction of all fixels based on the Jacobian matrix (local affine transformation) at each voxel in the warp. Note that in-place fixel reorientation can be performed by specifing the output fixel folder to be the same as the input, and using the :code:`-force` option:: - fixelreorient + fixelreorient 11. Assign subject fixels to template fixels ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 81eebf8d44c4a1c35e26adb4f31b5d6815803904 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Sun, 11 Sep 2016 13:11:47 +1000 Subject: [PATCH 117/723] Added fixelconvert, a command to convert old msf files into the new sparse folder format --- cmd/fixelconvert.cpp | 129 +++++++++++++++++++++++ docs/reference/commands/fixelconvert.rst | 62 +++++++++++ 2 files changed, 191 insertions(+) create mode 100644 cmd/fixelconvert.cpp create mode 100644 docs/reference/commands/fixelconvert.rst diff --git a/cmd/fixelconvert.cpp b/cmd/fixelconvert.cpp new file mode 100644 index 0000000000..e00d91af59 --- /dev/null +++ b/cmd/fixelconvert.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + +#include "command.h" +#include "image.h" +#include "progressbar.h" + +#include "algo/loop.h" + +#include "math/SH.h" + +#include "fixel_format/helpers.h" +#include "fixel_format/keys.h" + +#include "sparse/fixel_metric.h" +#include "sparse/keys.h" +#include "sparse/image.h" + + +using namespace MR; +using namespace App; + +using Sparse::FixelMetric; + + +void usage () +{ + + AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; + + DESCRIPTION + + "convert an old format fixel image (*.msf) to the new fixel folder format"; + + ARGUMENTS + + Argument ("fixel_in", "the input fixel file.").type_image_in () + + Argument ("fixel_output", "the output fixel folder.").type_text(); + + OPTIONS + + 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 ("size", "also output the 'size' field from the old format"); +} + + +void run () +{ + Header header (Header::open (argument[0])); + header.keyval().erase (Sparse::name_key); + header.keyval().erase (Sparse::size_key); + + Sparse::Image input (argument[0]); + + std::string file_extension (".mif"); + if (get_options ("nii").size()) + file_extension = ".nii"; + + std::string value_name ("value"); + auto opt = get_options ("name"); + if (opt.size()) + value_name = std::string (opt[0][0]); + + const bool output_size = get_options ("size").size(); + + std::string output_fixel_folder = argument[1]; + FixelFormat::check_fixel_folder (output_fixel_folder, true); + + uint32_t fixel_count = 0; + for (auto i = Loop (input) (input); i; ++i) + fixel_count += input.value().size(); + + 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(); + + Header directions_header (data_header); + directions_header.size(1) = 3; + + header.keyval()[FixelFormat::n_fixels_key] = str(fixel_count); + header.ndim() = 4; + header.size(3) = 2; + header.datatype() = DataType::from(); + header.datatype().set_byte_order_native(); + + auto index_image = Image::create (Path::join (output_fixel_folder, "index" + file_extension), header); + auto directions_image = Image::create (Path::join (output_fixel_folder, "directions" + file_extension), directions_header).with_direct_io(); + auto value_image = Image::create (Path::join (output_fixel_folder, value_name + file_extension), data_header); + Image size_image; + if (output_size) + size_image = Image::create (Path::join (output_fixel_folder, "size" + file_extension), data_header); + + int32_t offset = 0; + for (auto i = Loop ("converting fixel format", input, 0, 3) (input, index_image); i; ++i) { + index_image.index(3) = 0; + index_image.value() = (int32_t)input.value().size(); + index_image.index(3) = 1; + index_image.value() = offset; + for (size_t f = 0; f != input.value().size(); ++f) { + directions_image.index(0) = offset; + directions_image.row(1) = input.value()[f].dir; + value_image.index(0) = offset; + value_image.value() = input.value()[f].value; + if (size_image.valid()) { + size_image.index(0) = offset; + size_image.value() = input.value()[f].size; + } + offset++; + } + } +} diff --git a/docs/reference/commands/fixelconvert.rst b/docs/reference/commands/fixelconvert.rst new file mode 100644 index 0000000000..d8f4a33e7a --- /dev/null +++ b/docs/reference/commands/fixelconvert.rst @@ -0,0 +1,62 @@ +.. _fixelconvert: + +fixelconvert +=========== + +Synopsis +-------- + +:: + + fixelconvert [ options ] fixel_in fixel_output + +- *fixel_in*: the input fixel file. +- *fixel_output*: the output fixel folder. + +Description +----------- + +convert an old format fixel image (*.msf) to the new fixel folder format + +Options +------- + +- **-name string** assign a different name to the value field output (Default: value). Do not include the file extension. + +- **-nii** output the index, directions and data file in NifTI format instead of *.mif + +- **-size** also output the 'size' field from the old format + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) + +- **-failonwarn** terminate program if a warning is produced + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +-------------- + + + +**Author:** David Raffelt (david.raffelt@florey.edu.au) + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + From 03a1d83fc333e0a32243391b90593e31b9d88bd9 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Sun, 11 Sep 2016 15:50:39 +1000 Subject: [PATCH 118/723] Removed tests for fixelcalc and fixelthreshold --- testing/tests/fixelcalc | 4 ---- testing/tests/fixelthreshold | 2 -- 2 files changed, 6 deletions(-) delete mode 100644 testing/tests/fixelcalc delete mode 100644 testing/tests/fixelthreshold diff --git a/testing/tests/fixelcalc b/testing/tests/fixelcalc deleted file mode 100644 index 063b7e061d..0000000000 --- a/testing/tests/fixelcalc +++ /dev/null @@ -1,4 +0,0 @@ -fixelcalc afd.msf add afd.msf tmp.msf --force; testing_diff_fixel tmp.msf fixelcalc/out1.msf 0 -fixelcalc afd.msf sub afd.msf tmp.msf --force; testing_diff_fixel tmp.msf fixelcalc/out2.msf 0 -fixelcalc afd.msf mult afd.msf tmp.msf --force; testing_diff_fixel tmp.msf fixelcalc/out3.msf 0 -fixelcalc afd.msf div afd.msf tmp.msf --force; testing_diff_fixel tmp.msf fixelcalc/out4.msf 0 diff --git a/testing/tests/fixelthreshold b/testing/tests/fixelthreshold deleted file mode 100644 index 42bb3374f1..0000000000 --- a/testing/tests/fixelthreshold +++ /dev/null @@ -1,2 +0,0 @@ -fixelthreshold afd.msf 0.5 tmp.msf --force; testing_diff_fixel tmp.msf fixelthreshold/out1.msf 0 -fixelthreshold afd.msf 0.5 tmp.msf -crop --force; testing_diff_fixel tmp.msf fixelthreshold/out2.msf 0 From 04f27156f4d1b9754525d185ae6ddd7ea0624dd2 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Sun, 11 Sep 2016 15:56:36 +1000 Subject: [PATCH 119/723] Fix a few issues introduced with stats type changes --- cmd/fixelcfestats.cpp | 51 +++++++++++++++++++------------------- lib/fixel_format/helpers.h | 21 ++++++++-------- src/stats/cfe.h | 2 +- 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index add6b18e72..11fa84bf1a 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -212,6 +212,7 @@ void run() { FixelFormat::copy_index_file (input_fixel_folder, output_fixel_folder); FixelFormat::copy_directions_file (input_fixel_folder, output_fixel_folder); + // Compute fixel-fixel connectivity std::vector > connectivity_matrix (num_fixels); std::vector fixel_TDI (num_fixels, 0.0); @@ -240,19 +241,21 @@ void run() { } track_file.close(); - // Normalise connectivity matrix and threshold, pre-compute fixel-fixel weights for smoothing. - std::vector > smoothing_weights (num_fixels); + std::vector > smoothing_weights (num_fixels); bool do_smoothing = false; - const value_type gaussian_const2 = 2.0 * smooth_std_dev * smooth_std_dev; - value_type gaussian_const1 = 1.0; + + const float gaussian_const2 = 2.0 * smooth_std_dev * smooth_std_dev; + float gaussian_const1 = 1.0; if (smooth_std_dev > 0.0) { do_smoothing = true; gaussian_const1 = 1.0 / (smooth_std_dev * std::sqrt (2.0 * Math::pi)); } + { ProgressBar progress ("normalising and thresholding fixel-fixel connectivity matrix", num_fixels); for (uint32_t fixel = 0; fixel < num_fixels; ++fixel) { + auto it = connectivity_matrix[fixel].begin(); while (it != connectivity_matrix[fixel].end()) { const value_type connectivity = it->second.value / value_type (fixel_TDI[fixel]); @@ -263,34 +266,32 @@ void run() { const value_type distance = std::sqrt (Math::pow2 (positions[fixel][0] - positions[it->first][0]) + Math::pow2 (positions[fixel][1] - positions[it->first][1]) + Math::pow2 (positions[fixel][2] - positions[it->first][2])); - const Stats::CFE::connectivity smoothing_weight (Stats::CFE::connectivity_value_type(connectivity * gaussian_const1 * std::exp (-std::pow (distance, 2) / gaussian_const2))); - if (smoothing_weight.value > connectivity_threshold) - smoothing_weights[fixel].insert (std::pair (it->first, smoothing_weight)); + const float smoothing_weight = connectivity * gaussian_const1 * std::exp (-std::pow (distance, 2) / gaussian_const2); + smoothing_weights[fixel].insert (std::pair (it->first, smoothing_weight)); } // Here we pre-exponentiate each connectivity value by C it->second.value = std::pow (connectivity, cfe_c); ++it; } } - // Make sure the fixel is fully connected to itself giving it a smoothing weight of 1 - Stats::CFE::connectivity self_connectivity; - self_connectivity.value = 1.0; - connectivity_matrix[fixel].insert (std::pair (fixel, self_connectivity)); - smoothing_weights[fixel].insert (std::pair (fixel, gaussian_const1)); + // Make sure the fixel is fully connected to itself + connectivity_matrix[fixel].insert (std::pair (fixel, Stats::CFE::connectivity (1.0))); + smoothing_weights[fixel].insert (std::pair (fixel, gaussian_const1)); + + // Normalise smoothing weights + value_type sum = 0.0; + for (auto it = smoothing_weights[fixel].begin(); it != smoothing_weights[fixel].end(); ++it) + sum += it->second; + value_type norm_factor = 1.0 / sum; + for (auto it = smoothing_weights[fixel].begin(); it != smoothing_weights[fixel].end(); ++it) { + it->second *= norm_factor; + if (it->second < 0.005) + smoothing_weights[fixel].erase (it++); + } progress++; } } - // Normalise smoothing weights - for (uint32_t fixel = 0; fixel < num_fixels; ++fixel) { - value_type sum = 0.0; - for (auto it = smoothing_weights[fixel].begin(); it != smoothing_weights[fixel].end(); ++it) - sum += it->second.value; - value_type norm_factor = 1.0 / sum; - for (auto it = smoothing_weights[fixel].begin(); it != smoothing_weights[fixel].end(); ++it) - it->second.value *= norm_factor; - } - // Load input data matrix_type data (num_fixels, identifiers.size()); { @@ -311,12 +312,10 @@ void run() { // Smooth the data for (size_t fixel = 0; fixel < num_fixels; ++fixel) { value_type value = 0.0; - std::map::const_iterator it = smoothing_weights[fixel].begin(); + std::map::const_iterator it = smoothing_weights[fixel].begin(); for (; it != smoothing_weights[fixel].end(); ++it) - value += subject_data_vector[it->first] * it->second.value; + value += subject_data_vector[it->first] * it->second; data (fixel, subject) = value; - if (!std::isfinite(value)) - std::cout << value << std::endl; } progress++; } diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index cf7eaa7202..06f09b7ebf 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -95,23 +95,22 @@ namespace MR inline void check_fixel_folder (const std::string &path, bool create_if_missing = false, bool check_if_empty = false) { - + std::string path_temp = path; // the user must be inside the fixel folder - if (path.empty()) { - - } + if (path.empty()) + path_temp = Path::cwd(); bool exists (true); - if (!(exists = Path::exists (path))) { - if (create_if_missing) File::mkdir (path); - else throw Exception ("Fixel directory (" + str(path) + ") does not exist"); + if (!(exists = Path::exists (path_temp))) { + if (create_if_missing) File::mkdir (path_temp); + else throw Exception ("Fixel directory (" + str(path_temp) + ") does not exist"); } - else if (!Path::is_dir (path)) - throw Exception (str(path) + " is not a directory"); + else if (!Path::is_dir (path_temp)) + throw Exception (str(path_temp) + " is not a directory"); - if (check_if_empty && Path::Dir (path).read_name ().size () != 0) - throw Exception ("Expected fixel directory " + path + " to be empty."); + if (check_if_empty && Path::Dir (path_temp).read_name ().size () != 0) + throw Exception ("Expected fixel directory " + path_temp + " to be empty."); } diff --git a/src/stats/cfe.h b/src/stats/cfe.h index b6f2319c3f..d7a526269b 100644 --- a/src/stats/cfe.h +++ b/src/stats/cfe.h @@ -45,7 +45,7 @@ namespace MR class connectivity { public: connectivity () : value (0.0) { } - connectivity (const connectivity_value_type v) : value (0.0) { } + connectivity (const connectivity_value_type v) : value (v) { } connectivity_value_type value; }; From ec1e208f9fc880385405343fd2b7baa7bc97deae Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 13 Sep 2016 12:11:22 +1000 Subject: [PATCH 120/723] dwipreproc: First attempt at volume recombination Since the script now aims to operate on any arbitrary organisation of image data, it must use the explicit determination of matching volume pairs, along with their phase encoding information, to derive the corresponding jacobians and perform volume recombination. Note that this update is UNTESTED and unlikely to work currently. --- scripts/dwipreproc | 90 ++++++++++++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 31 deletions(-) diff --git a/scripts/dwipreproc b/scripts/dwipreproc index 79f3c3f997..9f7d9aa01d 100755 --- a/scripts/dwipreproc +++ b/scripts/dwipreproc @@ -461,6 +461,7 @@ else: # Run eddy +# TODO Detect eddy version and use outlier replacement if available runCommand('mrconvert dwi.mif' + import_dwi_pe_table_option + ' dwi.nii -stride -1,+2,+3,+4 -export_grad_fsl bvecs bvals -export_pe_eddy eddy_config.txt eddy_indices.txt') delFile('dwi.mif') runCommand(eddy_cmd + ' --imain=dwi.nii --mask=mask.nii --acqp=eddy_config.txt --index=eddy_indices.txt --bvecs=bvecs --bvals=bvals' + eddy_in_topup_option + ' --out=dwi_post_eddy') @@ -513,17 +514,20 @@ else: # Particularly if eddy has provided rotated bvecs, since we're combining two volumes into one that # potentially have subject rotation between them (and therefore the sensitisation direction is # not precisely equivalent), the best we can do is take the mean of the two vectors. - # TODO Re-test LSR - # TODO Manual recombination of volumes needs to take into account the explicit volume matching + # Manual recombination of volumes needs to take into account the explicit volume matching + # TODO Re-test eddy LSR + bvecs = [ [] for axis in range(3) ] with open(bvecs_path, 'r') as f: for axis, line in enumerate(f): bvecs[axis] = line.split() - bvecs_combined_transpose = [ [] * num_volumes ] - for volume in range(0, num_volumes): - bvec_sum = [ float(bvecs[0][volume]) + float(bvecs[0][volume+num_volumes]), - float(bvecs[1][volume]) + float(bvecs[1][volume+num_volumes]), - float(bvecs[2][volume]) + float(bvecs[2][volume+num_volumes]) ] + + bvecs_combined_transpose = [ [] * num_volumes/2 ] + bvals_combined = [] * num_volumes/2 + for index, pair in enumerate(volume_pairs): + bvec_sum = [ float(bvecs[0][pair[0]]) + float(bvecs[0][pair[1]]), + float(bvecs[1][pair[0]]) + float(bvecs[1][pair[1]]), + float(bvecs[2][pair[0]]) + float(bvecs[2][pair[1]]) ] norm2 = bvec_sum[0]*bvec_sum[0] + bvec_sum[1]*bvec_sum[1] + bvec_sum[2]*bvec_sum[2] # Occasionally a bzero volume can have a zero vector if norm2: @@ -531,7 +535,9 @@ else: new_vec = [ bvec_sum[0]*factor, bvec_sum[1]*factor, bvec_sum[2]*factor ] else: new_vec = [ 0.0, 0.0, 0.0 ] - bvecs_combined_transpose[volume] = new_vec + bvecs_combined_transpose[index] = new_vec + bvals_combined[index] = 0.5 * (grad[pair[0]][3] + grad[pair[1]][3]) + with open('bvecs_combined', 'w') as f: for axis in range(0, 3): axis_data = [ ] @@ -539,12 +545,8 @@ else: axis_data.append(str(bvecs_combined_transpose[volume][axis])) f.write(' '.join(axis_data) + '\n') - bvals = [ ] - with open('bvals', 'r') as f: - bvals = f.read().split() - bvals = bvals[0:num_volumes] with open('bvals_combined', 'w') as f: - f.write(' '.join(bvals)) + f.write(' '.join(bvals_combined)) @@ -570,24 +572,50 @@ else: # Scaling term for field map is identical to the bandwidth provided in the topup config file # (converts Hz to pixel count; that way a simple image gradient can be used to get the Jacobians) # Let mrfilter apply the default 1 voxel size gaussian smoothing filter before calculating the field gradient - runCommand('mrcalc ' + field_map_image + ' 0.1 -mult - | mrfilter - gradient - | mrconvert - field_deriv_pe.mif -coord 3 1 -axes 0,1,2') - delFile(field_map_image) - runCommand('mrcalc 1.0 field_deriv_pe.mif -add 0.0 -max jacobian1.mif') - runCommand('mrcalc 1.0 field_deriv_pe.mif -sub 0.0 -max jacobian2.mif') - delFile('field_deriv_pe.mif') - runCommand('mrcalc jacobian1.mif jacobian1.mif -mult weight1.mif') - runCommand('mrcalc jacobian2.mif jacobian2.mif -mult weight2.mif') - delFile('jacobian1.mif') - delFile('jacobian2.mif') - runCommand('mrcalc weight1.mif weight2.mif -add sum_weights.mif') - - - - # Manually combine corresponding volumes from EDDY output - runCommand('mrconvert dwi_post_eddy' + fsl_suffix + ' corrected1.mif -coord 3 0:' + str(num_volumes-1)) - runCommand('mrconvert dwi_post_eddy' + fsl_suffix + ' corrected2.mif -coord 3 ' + str(num_volumes) + ':' + str((num_volumes*2)-1)) - delFile('dwi_post_eddy' + fsl_suffix) - runCommand('mrcalc corrected1.mif weight1.mif -mult corrected2.mif weight2.mif -mult -add sum_weights.mif -divide 0.0 -max - | mrconvert - result.mif -fslgrad bvecs_combined bvals_combined' + stride_option) + # + # The jacobian image may be different for any particular volume pair + # The appropriate PE directions and total readout times can be acquired from the eddy-style config/index files + # eddy_config.txt and eddy_indices.txt + + eddy_config = [ [ float(f) for f in line ] for line in open('eddy_config.txt', 'r').read().split('\n') ] + eddy_indices = [ int(i) for i in open('eddy_indices.txt', 'r').read() ] + + # This section derives, for each phase encoding configuration present, the 'weight' to be applied + # to the image during volume recombination, which is based on the Jacobian of the field in the + # phase encoding direction + for index, config in enumerate(eddy_config): + pe_axis = [ i for i, e in enumerate(config[0:3]) if e != 0][0] + sign_multiplier = ' -1.0 -mult' if config[pe_axis] < 0 else '' + field_derivative_path = 'field_deriv_pe_' + str(index) + 'mif' + runCommand('mrcalc ' + field_map_image + ' ' + str(config[3]) + ' -mult' + sign_multiplier + ' - | mrfilter - gradient - | mrconvert - ' + field_derivative_path + ' -coord 3 ' + str(pe_axis) + ' -axes 0,1,2') + jacobian_path = 'jacobian_' + str(index) + '.mif' + runCommand('mrcalc 1.0 ' + field_derivative_path + ' -add 0.0 -max ' + jacobian_path) + delFile(field_derivative_path) + runCommand('mrcalc ' + jacobian_path + ' ' + jacobian_path + ' -mult weight' + str(index) + '.mif') + delFile(jacobian_path) + + # This section extracts the two volumes corresponding to each reversed phase-encoded volume pair, and + # derives a single image volume based on the recombination equation + combined_image_list = [ ] + for index, volumes in enumerate(volume_pairs): + config_lines = [ eddy_config[i] for i in volumes ] + runCommand('mrconvert dwi_post_eddy' + fsl_suffix + ' volume0.mif -coord 3 ' + volumes[0]) + runCommand('mrconvert dwi_post_eddy' + fsl_suffix + ' volume1.mif -coord 3 ' + volumes[1]) + # Volume recombination equation described in Skare and Bammer 2010 + runCommand('mrcalc volume0.mif weight' + str(volumes[0]) + '.mif -mult volumes1.mif weight' + str(volumes[1]) + '.mif -mult -add weight' + str(volumes[0]) + '.mif weight' + str(volumes[1]) + '.mif -add -divide 0.0 -max combined' + str(index) + '.mif') + combined_image_list.append('combined' + str(index) + '.mif') + os.unlink('volume0.mif') + os.unlink('volume1.mif') + + for index in range(0, len(eddy_config): + delFile('weight' + str(index) + '.mif') + + # Finally the recombined volumes must be concatenated to produce the resulting image series + runCommand('mrcat ' + ' '.join(combined_image_list) + ' - -axis 3 | mrconvert - result.mif -fslgrad bvecs_combined bvals_combined' + stride_option) + + for path in combined_image_list: + delFile(path) + From 0cdf1089db176011c7ee36881274d9c91358de45 Mon Sep 17 00:00:00 2001 From: rtabbara Date: Wed, 14 Sep 2016 10:47:15 +1000 Subject: [PATCH 121/723] Vector plot: Adding ability to open fixel folders solely with index/direction - That is, there are no corresponding fixel values files --- src/gui/mrview/tool/vector/fixel.cpp | 14 +++++++++----- src/gui/mrview/tool/vector/fixel.h | 11 ++++++++--- src/gui/mrview/tool/vector/fixelfolder.cpp | 3 --- src/gui/mrview/tool/vector/vector.cpp | 16 +++++++++++++++- src/gui/mrview/tool/vector/vector_structs.h | 4 ++-- 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/gui/mrview/tool/vector/fixel.cpp b/src/gui/mrview/tool/vector/fixel.cpp index a2b64a4b36..5c7400e1fd 100644 --- a/src/gui/mrview/tool/vector/fixel.cpp +++ b/src/gui/mrview/tool/vector/fixel.cpp @@ -337,6 +337,7 @@ namespace MR const auto& val_buffer = current_fixel_value_state ().buffer_store; const auto& col_buffer = current_fixel_colour_state ().buffer_store; const auto& threshold_buffer = current_fixel_threshold_state ().buffer_store; + bool has_val = has_values (); for (int y = -ny; y <= ny; ++y) { for (int x = -nx; x <= nx; ++x) { @@ -357,7 +358,8 @@ namespace MR regular_grid_buffer_val.push_back (val_buffer[index]); if (colour_type == CValue) regular_grid_buffer_colour.push_back (col_buffer[index]); - regular_grid_buffer_threshold.push_back (threshold_buffer[index]); + if (has_val) + regular_grid_buffer_threshold.push_back (threshold_buffer[index]); } } } @@ -400,11 +402,13 @@ namespace MR } // fixel threshold - regular_grid_threshold_buffer.bind (gl::ARRAY_BUFFER); - gl::BufferData (gl::ARRAY_BUFFER, regular_grid_buffer_threshold.size () * sizeof(float), + if (has_val) { + regular_grid_threshold_buffer.bind (gl::ARRAY_BUFFER); + gl::BufferData (gl::ARRAY_BUFFER, regular_grid_buffer_threshold.size () * sizeof(float), ®ular_grid_buffer_threshold[0], gl::DYNAMIC_DRAW); - gl::EnableVertexAttribArray (4); - gl::VertexAttribPointer (4, 1, gl::FLOAT, gl::FALSE_, 0, (void*)0); + gl::EnableVertexAttribArray (4); + gl::VertexAttribPointer (4, 1, gl::FLOAT, gl::FALSE_, 0, (void*)0); + } ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; } diff --git a/src/gui/mrview/tool/vector/fixel.h b/src/gui/mrview/tool/vector/fixel.h index c72022d9b4..fec1f9a814 100644 --- a/src/gui/mrview/tool/vector/fixel.h +++ b/src/gui/mrview/tool/vector/fixel.h @@ -211,6 +211,10 @@ namespace MR combo_box.setCurrentIndex (threshold_type_index); } + bool has_values () const { + return fixel_values.size(); + } + protected: struct IntPointHasher { size_t operator () (const std::array& v) const { @@ -226,15 +230,15 @@ namespace MR void update_interp_image_buffer (const Projection&, const MR::Header&, const MR::Transform&); inline FixelValue& current_fixel_value_state () const { - return fixel_values[value_types[scale_type_index]]; + return fixel_values.size() ? fixel_values[value_types[scale_type_index]] : dummy_fixel_val_state; } inline FixelValue& current_fixel_threshold_state () const { - return fixel_values[threshold_types[threshold_type_index]]; + return fixel_values.size() ? fixel_values[threshold_types[threshold_type_index]] : dummy_fixel_val_state; } inline FixelValue& current_fixel_colour_state () const { - return fixel_values[colour_types[colour_type_index]]; + return fixel_values.size() ? fixel_values[colour_types[colour_type_index]] : dummy_fixel_val_state; } MR::Header header; @@ -242,6 +246,7 @@ namespace MR std::vector value_types; std::vector threshold_types; mutable std::map fixel_values; + mutable FixelValue dummy_fixel_val_state; std::vector pos_buffer_store; std::vector dir_buffer_store; diff --git a/src/gui/mrview/tool/vector/fixelfolder.cpp b/src/gui/mrview/tool/vector/fixelfolder.cpp index a75b0f7a78..13d8266906 100644 --- a/src/gui/mrview/tool/vector/fixelfolder.cpp +++ b/src/gui/mrview/tool/vector/fixelfolder.cpp @@ -101,9 +101,6 @@ namespace MR } } - - if (!fixel_values.size ()) - throw InvalidImageException ("Fixel index image " + fixel_data->name () + " has no associated image data files"); } } diff --git a/src/gui/mrview/tool/vector/vector.cpp b/src/gui/mrview/tool/vector/vector.cpp index eadc80f8b0..5c277136f7 100644 --- a/src/gui/mrview/tool/vector/vector.cpp +++ b/src/gui/mrview/tool/vector/vector.cpp @@ -522,9 +522,23 @@ namespace MR AbstractFixel* first_fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[0])); - if (n_images == 1 && reload_threshold_types) + bool has_val = first_fixel->has_values (); + + if (n_images == 1 && reload_threshold_types && has_val) first_fixel->load_threshold_combobox_options (*threshold_combobox); + threshold_lower->setEnabled (has_val); + threshold_upper->setEnabled (has_val); + threshold_lower_box->setEnabled (has_val); + threshold_upper_box->setEnabled (has_val); + threshold_combobox->setEnabled (has_val); + + if (!has_val) { + threshold_lower_box->setChecked (false); + threshold_upper_box->setChecked (false); + return; + } + if (!std::isfinite (first_fixel->get_unscaled_threshold_lower ())) first_fixel->lessthan = first_fixel->intensity_min (); if (!std::isfinite (first_fixel->get_unscaled_threshold_upper ())) diff --git a/src/gui/mrview/tool/vector/vector_structs.h b/src/gui/mrview/tool/vector/vector_structs.h index f3988a981c..cd7b3bdc71 100644 --- a/src/gui/mrview/tool/vector/vector_structs.h +++ b/src/gui/mrview/tool/vector/vector_structs.h @@ -30,8 +30,8 @@ namespace MR struct FixelValue { float value_min = std::numeric_limits::max (); float value_max = std::numeric_limits::min (); - float lessthan, greaterthan; - float current_min, current_max; + float lessthan = value_min, greaterthan = value_max; + float current_min = value_min, current_max = value_max; std::vector buffer_store; From b42a675b74ec7fad95c73b5df59c12afb6410e02 Mon Sep 17 00:00:00 2001 From: rtabbara Date: Wed, 14 Sep 2016 11:09:40 +1000 Subject: [PATCH 122/723] Vector plot: Ensuring thresholding does not affect sole index/dir fixelfolders when using multiple selection --- src/gui/mrview/tool/vector/vector.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/gui/mrview/tool/vector/vector.cpp b/src/gui/mrview/tool/vector/vector.cpp index 5c277136f7..5c80fa7cfd 100644 --- a/src/gui/mrview/tool/vector/vector.cpp +++ b/src/gui/mrview/tool/vector/vector.cpp @@ -714,8 +714,10 @@ namespace MR if (threshold_lower_box->checkState() == Qt::PartiallyChecked) return; threshold_lower->setEnabled (threshold_lower_box->isChecked()); QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->set_use_discard_lower (threshold_lower_box->isChecked()); + for (int i = 0; i < indices.size(); ++i) { + auto& fixel_image = *fixel_list_model->get_fixel_image (indices[i]); + fixel_image.set_use_discard_lower (threshold_lower_box->isChecked() && fixel_image.has_values ()); + } window().updateGL(); } @@ -725,8 +727,10 @@ namespace MR if (threshold_upper_box->checkState() == Qt::PartiallyChecked) return; threshold_upper->setEnabled (threshold_upper_box->isChecked()); QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); - for (int i = 0; i < indices.size(); ++i) - fixel_list_model->get_fixel_image (indices[i])->set_use_discard_upper (threshold_upper_box->isChecked()); + for (int i = 0; i < indices.size(); ++i) { + auto& fixel_image = *fixel_list_model->get_fixel_image (indices[i]); + fixel_image.set_use_discard_upper (threshold_upper_box->isChecked() && fixel_image.has_values ()); + } window().updateGL(); } @@ -737,8 +741,11 @@ namespace MR if (threshold_lower_box->isChecked()) { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); for (int i = 0; i < indices.size(); ++i) { - fixel_list_model->get_fixel_image (indices[i])->set_threshold_lower (threshold_lower->value()); - fixel_list_model->get_fixel_image (indices[i])->set_use_discard_lower (threshold_lower_box->isChecked()); + auto& fixel_image = *fixel_list_model->get_fixel_image (indices[i]); + if (fixel_image.has_values ()) { + fixel_image.set_threshold_lower (threshold_lower->value()); + fixel_image.set_use_discard_lower (threshold_lower_box->isChecked()); + } } window().updateGL(); } @@ -751,8 +758,11 @@ namespace MR if (threshold_upper_box->isChecked()) { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); for (int i = 0; i < indices.size(); ++i) { - fixel_list_model->get_fixel_image (indices[i])->set_threshold_upper (threshold_upper->value()); - fixel_list_model->get_fixel_image (indices[i])->set_use_discard_upper (threshold_upper_box->isChecked()); + auto& fixel_image = *fixel_list_model->get_fixel_image (indices[i]); + if (fixel_image.has_values ()) { + fixel_image.set_threshold_upper (threshold_upper->value()); + fixel_image.set_use_discard_upper (threshold_upper_box->isChecked()); + } } window().updateGL(); } From fb9e0e428b9dce139d31219345bbbf2ac20a29a0 Mon Sep 17 00:00:00 2001 From: rtabbara Date: Wed, 14 Sep 2016 13:46:29 +1000 Subject: [PATCH 123/723] Vector plot: FixelFolder: load fixel data files lazily upon request - means the vector plot tool will not choke when loading a fixel-folder with a large number of data files --- src/gui/mrview/tool/vector/fixel.h | 10 +++- src/gui/mrview/tool/vector/fixelfolder.cpp | 63 ++++++++++++++++----- src/gui/mrview/tool/vector/fixelfolder.h | 3 + src/gui/mrview/tool/vector/vector_structs.h | 1 + 4 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/gui/mrview/tool/vector/fixel.h b/src/gui/mrview/tool/vector/fixel.h index fec1f9a814..2ff7344624 100644 --- a/src/gui/mrview/tool/vector/fixel.h +++ b/src/gui/mrview/tool/vector/fixel.h @@ -230,15 +230,19 @@ namespace MR void update_interp_image_buffer (const Projection&, const MR::Header&, const MR::Transform&); inline FixelValue& current_fixel_value_state () const { - return fixel_values.size() ? fixel_values[value_types[scale_type_index]] : dummy_fixel_val_state; + return get_fixel_value (value_types[scale_type_index]); } inline FixelValue& current_fixel_threshold_state () const { - return fixel_values.size() ? fixel_values[threshold_types[threshold_type_index]] : dummy_fixel_val_state; + return get_fixel_value (threshold_types[threshold_type_index]); } inline FixelValue& current_fixel_colour_state () const { - return fixel_values.size() ? fixel_values[colour_types[colour_type_index]] : dummy_fixel_val_state; + return get_fixel_value (colour_types[colour_type_index]); + } + + virtual FixelValue& get_fixel_value (const std::string& key) const { + return fixel_values[key]; } MR::Header header; diff --git a/src/gui/mrview/tool/vector/fixelfolder.cpp b/src/gui/mrview/tool/vector/fixelfolder.cpp index 13d8266906..d5870e69a7 100644 --- a/src/gui/mrview/tool/vector/fixelfolder.cpp +++ b/src/gui/mrview/tool/vector/fixelfolder.cpp @@ -73,37 +73,70 @@ namespace MR } } - // Load fixel data images + // Load fixel data images keys + // We will load the actual fixel data lazily upon request auto data_headers = FixelFormat::find_data_headers (Path::dirname (fixel_data->name ()), *fixel_data); for (auto& header : data_headers) { if (header.size (1) != 1) continue; - auto data_image = header.get_image (); const auto data_key = Path::basename (header.name ()); fixel_values[data_key]; value_types.push_back (data_key); colour_types.push_back (data_key); threshold_types.push_back (data_key); + } + } - data_image.index (1) = 0; - for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { - fixel_data->index (3) = 0; - const size_t nfixels = fixel_data->value (); - fixel_data->index (3) = 1; - const size_t offset = fixel_data->value (); - - for (size_t f = 0; f < nfixels; ++f) { - data_image.index (0) = offset + f; - float value = data_image.value (); - fixel_values[data_key].add_value (value); - } - } + void FixelFolder::lazy_load_fixel_value_file (const std::string& key) const { + + // We're assuming the key corresponds to the fixel data filename + const auto data_filepath = Path::join(Path::dirname (fixel_data->name ()), key); + fixel_values[key].loaded = true; + + if (!Path::exists (data_filepath)) + return; + + auto H = Header::open (data_filepath); + if (!FixelFormat::is_data_file (H)) + return; + + auto data_image = H.get_image (); + + data_image.index (1) = 0; + for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { + fixel_data->index (3) = 0; + const size_t nfixels = fixel_data->value (); + fixel_data->index (3) = 1; + const size_t offset = fixel_data->value (); + + for (size_t f = 0; f < nfixels; ++f) { + data_image.index (0) = offset + f; + float value = data_image.value (); + fixel_values[key].add_value (value); + } } + + fixel_values[key].initialise_windowing (); + } + + + FixelValue& FixelFolder::get_fixel_value (const std::string& key) const { + if (!has_values ()) + return dummy_fixel_val_state; + + FixelValue& fixel_val = fixel_values[key]; + // Buffer hasn't been loaded yet - we do this lazily + if (!fixel_val.loaded) + lazy_load_fixel_value_file (key); + + return fixel_val; } } + + } } } diff --git a/src/gui/mrview/tool/vector/fixelfolder.h b/src/gui/mrview/tool/vector/fixelfolder.h index 4fcd427324..9316be8adb 100644 --- a/src/gui/mrview/tool/vector/fixelfolder.h +++ b/src/gui/mrview/tool/vector/fixelfolder.h @@ -39,6 +39,9 @@ namespace MR } void load_image_buffer () override; + FixelValue& get_fixel_value (const std::string& key) const override; + protected: + void lazy_load_fixel_value_file (const std::string& key) const; }; } } diff --git a/src/gui/mrview/tool/vector/vector_structs.h b/src/gui/mrview/tool/vector/vector_structs.h index cd7b3bdc71..de779870a2 100644 --- a/src/gui/mrview/tool/vector/vector_structs.h +++ b/src/gui/mrview/tool/vector/vector_structs.h @@ -28,6 +28,7 @@ namespace MR enum FixelScaleType { Unity, Value }; struct FixelValue { + bool loaded = false; float value_min = std::numeric_limits::max (); float value_max = std::numeric_limits::min (); float lessthan = value_min, greaterthan = value_max; From 01f49c64198937651e66b7e9e98e49e3c5261979 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 14 Sep 2016 14:49:46 +1000 Subject: [PATCH 124/723] update notfound script to handle symlinked directories --- scripts/notfound | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/notfound b/scripts/notfound index 67c1d08352..e1792694f8 100755 --- a/scripts/notfound +++ b/scripts/notfound @@ -19,5 +19,5 @@ exit 1 fi -find ${1} -mindepth 1 -maxdepth 2 -type d '!' -exec test -e "{}/${2}" ';' -print +find ${1} -mindepth 1 -maxdepth 2 \( -type l -o -type d \) '!' -exec test -e "{}/${2}" ';' -print From 031dcbe3f71af17c57b2f6617b2fa311f647d346 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 14 Sep 2016 16:29:47 +1000 Subject: [PATCH 125/723] fixel2sh, update to use FixelLoop --- cmd/fixel2sh.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/cmd/fixel2sh.cpp b/cmd/fixel2sh.cpp index 736c97b540..9d530e78dd 100644 --- a/cmd/fixel2sh.cpp +++ b/cmd/fixel2sh.cpp @@ -24,6 +24,7 @@ #include "fixel_format/helpers.h" #include "fixel_format/keys.h" +#include "fixel_format/loop.h" using namespace MR; using namespace App; @@ -77,15 +78,8 @@ void run () 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); - in_index_image.index(3) = 0; - uint32_t num_fixels_in_voxel = in_index_image.value(); - in_index_image.index(3) = 1; - uint32_t offset = in_index_image.value(); - - for (size_t fixel = 0; fixel < num_fixels_in_voxel; ++fixel) { - in_directions_image.index(0) = offset + fixel; + for (auto f = FixelFormat::FixelLoop (in_index_image) (in_directions_image, in_data_image); f; ++f) { apsf_values = aPSF (apsf_values, in_directions_image.row(1)); - in_data_image.index(0) = offset + fixel; 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; From 30528950100f6e176f05658d166338365cf66518 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 14 Sep 2016 16:46:40 +1000 Subject: [PATCH 126/723] few improvements to fixelcrop help --- cmd/fixelcrop.cpp | 5 +++-- docs/reference/commands/fixelcrop.rst | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/fixelcrop.cpp b/cmd/fixelcrop.cpp index 30b5eda411..18977bc474 100644 --- a/cmd/fixelcrop.cpp +++ b/cmd/fixelcrop.cpp @@ -32,11 +32,12 @@ void usage () AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) & Rami Tabarra (rami.tabarra@florey.edu.au)"; DESCRIPTION - + "Crop a fixel index image (i.e. remove fixels) using a fixel mask"; + + "Crop/remove fixels from sparse fixel image using a binary fixel mask. The mask must be input as a " + "fixel data file the same dimensions as the fixel data file(s) to be cropped."; ARGUMENTS + Argument ("input_fixel_folder", "the input fixel folder file to be cropped").type_text () - + Argument ("input_fixel_data_mask", "the input fixel data file to be cropped").type_image_in () + + Argument ("input_fixel_mask", "the input fixel data file to be cropped").type_image_in () + Argument ("output_fixel_folder", "the output fixel folder").type_text (); } diff --git a/docs/reference/commands/fixelcrop.rst b/docs/reference/commands/fixelcrop.rst index 1f4b27e196..365956b0cb 100644 --- a/docs/reference/commands/fixelcrop.rst +++ b/docs/reference/commands/fixelcrop.rst @@ -8,16 +8,16 @@ Synopsis :: - fixelcrop [ options ] input_fixel_folder input_fixel_data_mask output_fixel_folder + fixelcrop [ options ] input_fixel_folder input_fixel_mask output_fixel_folder - *input_fixel_folder*: the input fixel folder file to be cropped -- *input_fixel_data_mask*: the input fixel data file to be cropped +- *input_fixel_mask*: the input fixel data file to be cropped - *output_fixel_folder*: the output fixel folder Description ----------- -Crop a fixel index image (i.e. remove fixels) using a fixel mask +Crop/remove fixels from sparse fixel image using a binary fixel mask. The mask must be input as a fixel data file the same dimensions as the fixel data file(s) to be cropped. Options ------- From 2e0e13b7025834eefea61b6595aa49605eaa5b26 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 14 Sep 2016 16:47:57 +1000 Subject: [PATCH 127/723] Ported warp2metric to new fixel format --- cmd/warp2metric.cpp | 83 ++++++++++++++----------- docs/reference/commands/warp2metric.rst | 2 +- 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/cmd/warp2metric.cpp b/cmd/warp2metric.cpp index 9df886c409..45c2e14f28 100644 --- a/cmd/warp2metric.cpp +++ b/cmd/warp2metric.cpp @@ -18,16 +18,15 @@ #include "image.h" #include "algo/threaded_loop.h" #include "image.h" -#include "sparse/fixel_metric.h" -#include "sparse/image.h" #include "adapter/jacobian.h" #include "registration/warp/helpers.h" +#include "fixel_format/helpers.h" +#include "fixel_format/keys.h" +#include "fixel_format/loop.h" using namespace MR; using namespace App; -using Sparse::FixelMetric; - void usage () { @@ -37,20 +36,21 @@ void usage () + "compute fixel or voxel-wise metrics from a 4D deformation field"; 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") - + Argument ("template_input").type_image_in () - + Argument ("output").type_image_out () + + Argument ("input_template_fixel_folder").type_image_in() + + Argument ("output_fixel_folder").type_text() + + Argument ("output_fixel_data_name").type_image_out() + 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 () + + Argument ("output").type_image_out() + Option ("jdet", "output the Jacobian determinant instead of the full matrix") - + Argument ("output").type_image_out (); + + Argument ("output").type_image_out(); //TODO add FC paper reference } @@ -64,22 +64,35 @@ void run () auto input = Image::open (argument[0]).with_direct_io (3); Registration::Warp::check_warp (input); - std::unique_ptr > jmatrix_output; - std::unique_ptr > jdeterminant_output; - std::unique_ptr > fixel_template; - std::unique_ptr > fc_output; + Image jmatrix_output; + Image jdeterminant_output; + Image fixel_template_index; + Image fixel_template_directions; + Image fc_output_data; auto opt = get_options ("fc"); if (opt.size()) { - Header output_header (input); + fixel_template_index = FixelFormat::find_index_header (opt[0][0]).get_image(); + fixel_template_directions = FixelFormat::find_directions_header (opt[0][0], fixel_template_index).get_image().with_direct_io(); + + std::string output_fixel_folder (opt[0][1]); + FixelFormat::copy_index_file (opt[0][0], output_fixel_folder, true); + FixelFormat::copy_directions_file (opt[0][0], output_fixel_folder, true); + + uint32_t num_fixels = 0; + fixel_template_index.index(0) = 0; + for (auto l = Loop (fixel_template_index, 0, 3) (fixel_template_index); l; ++l) + num_fixels += fixel_template_index.value(); + + Header output_header (fixel_template_index); output_header.ndim() = 3; - output_header.datatype() = DataType::UInt64; + output_header.size(0) = num_fixels; + output_header.size(1) = 1; + output_header.size(2) = 1; + output_header.datatype() = DataType::Float32; output_header.datatype().set_byte_order_native(); - output_header.keyval()[Sparse::name_key] = str(typeid(FixelMetric).name()); - output_header.keyval()[Sparse::size_key] = str(sizeof(FixelMetric)); - fixel_template.reset (new Sparse::Image (opt[0][0])); - fc_output.reset (new Sparse::Image (opt[0][1], output_header)); + fc_output_data = Image::create (Path::join(opt[0][1], opt[0][2]), output_header); } @@ -87,17 +100,17 @@ void run () if (opt.size()) { Header output_header (input); output_header.size(3) = 9; - jmatrix_output.reset (new Image (Image::create (opt[0][0], output_header))); + jmatrix_output = Image::create (opt[0][0], output_header); } opt = get_options ("jdet"); if (opt.size()) { Header output_header (input); output_header.ndim() = 3; - jdeterminant_output.reset (new Image (Image::create (opt[0][0], output_header))); + jdeterminant_output = Image::create (opt[0][0], output_header); } - if (!(jmatrix_output || jdeterminant_output || fc_output)) + if (!(jmatrix_output.valid() || jdeterminant_output.valid() || fc_output_data.valid())) throw Exception ("Nothing to do; please specify at least one output image type"); Adapter::Jacobian > jacobian (input); @@ -105,27 +118,25 @@ void run () for (auto i = Loop ("outputting warp metric(s)", jacobian, 0, 3) (jacobian); i; ++i) { auto jacobian_matrix = jacobian.value(); - if (fc_output) { - assign_pos_of (jacobian, 0, 3).to (*fc_output, *fixel_template); - fc_output->value().set_size (fixel_template->value().size()); - for (size_t f = 0; f != fixel_template->value().size(); ++f) { - fc_output->value()[f] = fixel_template->value()[f]; - Eigen::Vector3f fixel_direction = fixel_template->value()[f].dir; + if (fc_output_data.valid()) { + assign_pos_of (jacobian, 0, 3).to (fixel_template_index); + for (auto f = FixelFormat::FixelLoop (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; - fc_output->value()[f].value = jacobian_matrix.determinant() / fixel_direction_transformed.norm(); + fc_output_data.value() = jacobian_matrix.determinant() / fixel_direction_transformed.norm(); } } - if (jmatrix_output) { - assign_pos_of (jacobian, 0, 3).to (*jmatrix_output); + if (jmatrix_output.valid()) { + 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]; + jmatrix_output.index(3) = j; + jmatrix_output.value() = jacobian_matrix.data()[j]; } } - if (jdeterminant_output) { - assign_pos_of (jacobian, 0, 3).to (*jdeterminant_output); - jdeterminant_output->value() = jacobian_matrix.determinant(); + if (jdeterminant_output.valid()) { + assign_pos_of (jacobian, 0, 3).to (jdeterminant_output); + jdeterminant_output.value() = jacobian_matrix.determinant(); } } } diff --git a/docs/reference/commands/warp2metric.rst b/docs/reference/commands/warp2metric.rst index 9834cfb007..0f0d319993 100644 --- a/docs/reference/commands/warp2metric.rst +++ b/docs/reference/commands/warp2metric.rst @@ -20,7 +20,7 @@ compute fixel or voxel-wise metrics from a 4D deformation field Options ------- -- **-fc template_input output** 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 +- **-fc input_template_fixel_folder output_fixel_folder output_fixel_data_name** 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 - **-jmat output** 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 From 76e8274175ab8dbf1e2fd475792f3770a2caa785 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 14 Sep 2016 17:45:49 +1000 Subject: [PATCH 128/723] more updates to warp2fixel and fixel-based analysis docs --- cmd/fixelcfestats.cpp | 2 +- cmd/fixelcorrespondence.cpp | 4 +-- cmd/fixelreorient.cpp | 4 +-- cmd/warp2metric.cpp | 22 ++++++++------ docs/reference/commands/warp2metric.rst | 2 +- docs/workflows/fixel_based_analysis.rst | 20 ++++++------- lib/fixel_format/helpers.h | 38 +++++++------------------ 7 files changed, 39 insertions(+), 53 deletions(-) diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 11fa84bf1a..d145b4775b 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -123,7 +123,7 @@ void write_fixel_output (const std::string& filename, const VectorType& data, const Header& header) { - assert (data.size() = header.size(2)); + assert (data.size() == header.size(2)); auto output = Image::create (filename, header); for (uint32_t i = 0; i < data.size(); ++i) { output.index(2) = i; diff --git a/cmd/fixelcorrespondence.cpp b/cmd/fixelcorrespondence.cpp index 8f4973d0f2..19142ca656 100644 --- a/cmd/fixelcorrespondence.cpp +++ b/cmd/fixelcorrespondence.cpp @@ -70,8 +70,8 @@ void run () check_dimensions (subject_index, template_index); std::string output_fixel_folder = argument[2]; - FixelFormat::copy_index_file (argument[1], output_fixel_folder, true); - FixelFormat::copy_directions_file (argument[1], output_fixel_folder, true); + FixelFormat::copy_index_file (argument[1], output_fixel_folder); + FixelFormat::copy_directions_file (argument[1], output_fixel_folder); Header output_data_header (template_directions); output_data_header.size(1) = 1; diff --git a/cmd/fixelreorient.cpp b/cmd/fixelreorient.cpp index 7f9bd4b722..b39a11ce8a 100644 --- a/cmd/fixelreorient.cpp +++ b/cmd/fixelreorient.cpp @@ -90,8 +90,8 @@ void run () } if (output_fixel_folder != input_fixel_folder) { - FixelFormat::copy_index_file (input_fixel_folder, output_fixel_folder, false); - FixelFormat::copy_all_data_files (input_fixel_folder, output_fixel_folder, false); + FixelFormat::copy_index_file (input_fixel_folder, output_fixel_folder); + FixelFormat::copy_all_data_files (input_fixel_folder, output_fixel_folder); } } diff --git a/cmd/warp2metric.cpp b/cmd/warp2metric.cpp index 45c2e14f28..f8570b2b39 100644 --- a/cmd/warp2metric.cpp +++ b/cmd/warp2metric.cpp @@ -40,13 +40,14 @@ void usage () 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") - + Argument ("input_template_fixel_folder").type_image_in() + "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_folder output_fixel_folder fc.mif") + + Argument ("template_fixel_folder").type_image_in() + Argument ("output_fixel_folder").type_text() - + Argument ("output_fixel_data_name").type_image_out() + + Argument ("output_fixel_data").type_image_out() + 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") + "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") @@ -73,12 +74,15 @@ void run () auto opt = get_options ("fc"); if (opt.size()) { - fixel_template_index = FixelFormat::find_index_header (opt[0][0]).get_image(); - fixel_template_directions = FixelFormat::find_directions_header (opt[0][0], fixel_template_index).get_image().with_direct_io(); + std::string template_fixel_folder (opt[0][0]); + fixel_template_index = FixelFormat::find_index_header (template_fixel_folder).get_image(); + fixel_template_directions = FixelFormat::find_directions_header (template_fixel_folder, fixel_template_index).get_image().with_direct_io(); std::string output_fixel_folder (opt[0][1]); - FixelFormat::copy_index_file (opt[0][0], output_fixel_folder, true); - FixelFormat::copy_directions_file (opt[0][0], output_fixel_folder, true); + if (template_fixel_folder != output_fixel_folder) { + FixelFormat::copy_index_file (template_fixel_folder, output_fixel_folder); + FixelFormat::copy_directions_file (template_fixel_folder, output_fixel_folder); + } uint32_t num_fixels = 0; fixel_template_index.index(0) = 0; @@ -92,7 +96,7 @@ void run () output_header.size(2) = 1; output_header.datatype() = DataType::Float32; output_header.datatype().set_byte_order_native(); - fc_output_data = Image::create (Path::join(opt[0][1], opt[0][2]), output_header); + fc_output_data = Image::create (Path::join(output_fixel_folder, opt[0][2]), output_header); } diff --git a/docs/reference/commands/warp2metric.rst b/docs/reference/commands/warp2metric.rst index 0f0d319993..e80f1e65f5 100644 --- a/docs/reference/commands/warp2metric.rst +++ b/docs/reference/commands/warp2metric.rst @@ -20,7 +20,7 @@ compute fixel or voxel-wise metrics from a 4D deformation field Options ------- -- **-fc input_template_fixel_folder output_fixel_folder output_fixel_data_name** 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 +- **-fc template_fixel_folder output_fixel_folder output_fixel_data** 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_folder output_fixel_folder fc.mif - **-jmat output** 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 diff --git a/docs/workflows/fixel_based_analysis.rst b/docs/workflows/fixel_based_analysis.rst index 950e756a1f..90eebd9f2c 100644 --- a/docs/workflows/fixel_based_analysis.rst +++ b/docs/workflows/fixel_based_analysis.rst @@ -98,7 +98,7 @@ Note that here we transform FOD images into template space *without* FOD reorien ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Here we segment each FOD lobe to identify the number and orientation of fixels in each voxel. The output also contains the apparent fibre density (AFD) value per fixel estimated as the FOD lobe integral (see `here `_ for details on FOD segmentation). Note that in the following steps we will use a more generic shortened acronym - Fibre Density (FD) instead of AFD for consistency with our recent work (paper under review):: - fod2fixel -mask + fod2fixel -mask -afd .. NOTE:: If you would like to perform fixel-based analysis of metrics derived from other diffusion MRI models (e.g. CHARMED), replace steps 8 & 9. For example, in step 8 you can warp preprocessed DW images (also without any reorientation). In step 9 you could then estimate your DWI model of choice. @@ -111,25 +111,25 @@ Here we reorient the direction of all fixels based on the Jacobian matrix (local 11. Assign subject fixels to template fixels ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In step 8 we obtained spatial correspondence between subject and template. In step 10 we corrected the fixel orientations to ensure angular correspondence of the segmented peaks of subject and template. Here, for each fixel in the template fixel analysis mask, we identify the corresponding fixel in each voxel of the subject image and assign the FD value of the subject fixel to the corresponding fixel in template space. If no fixel exists in the subject that corresponds to the template fixel then it is assigned a value of zero. See `this paper `_ for more information:: +In step 8 we obtained spatial correspondence between subject and template. In step 10 we corrected the fixel orientations to ensure angular correspondence of the segmented peaks of subject and template. Here, for each fixel in the template fixel analysis mask, we identify the corresponding fixel in each voxel of the subject image and assign the FD value of the subject fixel to the corresponding fixel in template space. If no fixel exists in the subject that corresponds to the template fixel then it is assigned a value of zero. See `this paper `_ for more information. In the command below, we recommend the :code:`output_fixel_folder` is the same folder for all subjects (called something like "all_subject_data". This folder can be directly input to the :code:`fixelcfestats` command in step 16 below:: - fixelcorrespondence + fixelcorrespondence -force 12. Compute fibre cross-section (FC) metric ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Apparent fibre density, and other related measures that are influenced by the quantity of restricted water, only permit the investigation of group differences in the number of axons that manifest as a change to *within-voxel* density. However, depending on the disease type and stage, changes to the number of axons may also manifest as macroscopic differences in brain morphology. This step computes a fixel-based metric related to morphological differences in fibre cross-section, where information is derived entirely from the warps generated during registration (paper under review):: +Apparent fibre density, and other related measures that are influenced by the quantity of restricted water, only permit the investigation of group differences in the number of axons that manifest as a change to *within-voxel* density. However, depending on the disease type and stage, changes to the number of axons may also manifest as macroscopic differences in brain morphology. This step computes a fixel-based metric related to morphological differences in fibre cross-section, where information is derived entirely from the warps generated during registration (paper in press). In the command below, we recommend the :code:`output_fixel_folder` is the same folder for all subjects (called something like "all_subject_data"). This folder can be the same as the output folder used in the previous step.:: - warp2metric -fc + warp2metric -fc -The FC files will be used in the next step. However, for group statistical analysis of FC we recommend taking the log (FC) to ensure data are centred about zero and normally distributed:: +Note that the FC files will be used in the next step. However, for group statistical analysis of FC we recommend taking the log (FC) to ensure data are centred about zero and normally distributed:: - fixellog + mrcalc -log 13. Compute a combined measure of fibre density and cross-section (FDC) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To account for changes to both within-voxel fibre density and macroscopic atrophy, fibre density and fibre cross-section must be combined (a measure we call fibre density & cross-section, FDC). This enables a more complete picture of group differences in white matter. Note that as discussed in our future work (under review), group differences in FD or FC alone must be interpreted with care in crossing-fibre regions. However group differences in FDC are more directly interpretable. To generate the combined measure we 'modulate' the FD by FC:: - fixelcalc mult + mrcalc -mult 14. Perform whole-brain fibre tractography on the FOD template ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -147,8 +147,8 @@ Perform SIFT to reduce tractography biases in the whole-brain tractogram:: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You will need to perform a separate analysis for FD, FC and FDC. Statistics is performed using `connectivity-based fixel enhancement `_ as follows:: - fixelcfestats - + fixelcfestats input_tracks_2_million_sift.tck + Where the input files.txt is a text file containing the file path and name of each input fixel file on a separate line. The line ordering should correspond to the lines in the design_matrix.txt. Note that for correlation analysis, a column of 1's will not be automatically included (as per FSL randomise). Note that fixelcfestats currently only accepts a single contrast. However if the opposite (negative) contrast is also required (i.e. a two-tailed test), then use the :code:`-neg` option. Several output files will generated all starting with the supplied prefix. 17. Visualise the results diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index 06f09b7ebf..c1eeb2667a 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -198,53 +198,35 @@ namespace MR //! Copy a file from one fixel folder into another. If the output folder already contains the same file //! then it is checked to make sure it's the same as the input (based on the number of fixels it contains) - inline void copy_fixel_file (const std::string& input_file_path, const std::string &output_folder, const bool check_existing_output = false) { + inline void copy_fixel_file (const std::string& input_file_path, const std::string &output_folder) { check_fixel_folder (output_folder, true); std::string output_path = Path::join (output_folder, Path::basename (input_file_path)); Header input_header = Header::open (input_file_path); - - // do not need to copy if the file already exists and has the same number of fixels - if (Path::exists (output_path) && check_existing_output) { - Header output_header = Header::open (output_path); - if (is_index_image (input_header)) { - if (input_header.keyval().at (n_fixels_key) != output_header.keyval().at (n_fixels_key)) - throw InvalidFixelDirectoryException ("Output fixel folder (" + output_folder + ") already contains fixel " - "index file with a different number of fixels to the generated output"); - } else if (is_data_file (input_header)) { - if (input_header.size(2) != output_header.size(2)) - throw InvalidFixelDirectoryException ("Output fixel folder (" + output_folder + ") already contains fixel " - "file " + input_header.name() + ") with a different number of fixels to the generated output"); - } else { - throw Exception ("Input file (" + input_header.name() + ") in fixel folder (" + output_folder + ") is not a valid fixel file"); - } - } else { - // create new directions file or overwrite with force - auto input_image = input_header.get_image(); - auto output_directions_image = Image::create (output_path, input_image); - threaded_copy (input_image, output_directions_image); - } + auto input_image = input_header.get_image(); + auto output_directions_image = Image::create (output_path, input_header); + threaded_copy (input_image, output_directions_image); } //! Copy the index file from one fixel folder into another. When check_existing_output //! is true, it will check if existing output exists and NOT overwrite as long as the number of fixels in the file is the same - inline void copy_index_file (const std::string &input_folder, const std::string &output_folder, const bool check_existing_output = false) { + inline void copy_index_file (const std::string &input_folder, const std::string &output_folder) { Header input_header = FixelFormat::find_index_header (input_folder); - copy_fixel_file (input_header.name(), output_folder, check_existing_output); + copy_fixel_file (input_header.name(), output_folder); } //! Copy the directions file from one fixel folder into another. When check_existing_output //! is true, it will check if existing output exists and NOT overwrite as long as the number of fixels in the file is the same - inline void copy_directions_file (const std::string &input_folder, const std::string &output_folder, const bool check_existing_output = false) { + inline void copy_directions_file (const std::string &input_folder, const std::string &output_folder) { Header input_header = FixelFormat::find_directions_header (input_folder, FixelFormat::find_index_header (input_folder)); - copy_fixel_file (input_header.name(), output_folder, check_existing_output); + copy_fixel_file (input_header.name(), output_folder); } //! Copy all data files in a fixel folder into another folder. Data files do not include the index or directions file. When check_existing_output //! is true, it will check if existing outputs exist and NOT overwrite as long as the number of fixels in the file is the same - inline void copy_all_data_files (const std::string &input_folder, const std::string &output_folder, const bool check_existing_output = false) { + inline void copy_all_data_files (const std::string &input_folder, const std::string &output_folder) { for (auto& input_header : FixelFormat::find_data_headers (input_folder, FixelFormat::find_index_header (input_folder))) - copy_fixel_file (input_header.name(), output_folder, check_existing_output); + copy_fixel_file (input_header.name(), output_folder); } //! open a data file. checks that a user has not input a fixel folder or index image From a75e3fecfff83a36aa14270afc2dafb3765c1bf3 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 15 Sep 2016 15:17:58 +1000 Subject: [PATCH 129/723] dwi2response: Check provided mask is non-empty --- scripts/dwi2response | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/dwi2response b/scripts/dwi2response index f83263c59d..f545e92f16 100755 --- a/scripts/dwi2response +++ b/scripts/dwi2response @@ -112,6 +112,8 @@ if os.path.exists('mask.mif'): mask_size = [ int(x) for x in getHeaderInfo('mask.mif', 'size').split() ] if not mask_size[:3] == dwi_size[:3]: errorMessage('Dimensions of provided mask image do not match DWI') + if int(getImageStat('mask.mif', 'count', 'mask.mif')) == 0: + errorMessage('Input mask does not contain any voxels') else: runCommand('dwi2mask dwi.mif mask.mif') From 481e8e6d5c192a917a160be1abcbdf1f6766fd7d Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 15 Sep 2016 15:19:49 +1000 Subject: [PATCH 130/723] Scripts: lib.app.complete(): Use stderr, and fix missing terminal colouring definitions --- scripts/lib/app.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/scripts/lib/app.py b/scripts/lib/app.py index e5c7575d6b..e5d073bcaf 100644 --- a/scripts/lib/app.py +++ b/scripts/lib/app.py @@ -188,7 +188,7 @@ def gotoTempDir(): def complete(): import os, shutil, sys from lib.printMessage import printMessage - global tempDir, workingDir + global colourClear, colourPrint, colourWarn, tempDir, workingDir printMessage('Changing back to original directory (' + workingDir + ')') os.chdir(workingDir) if cleanup and tempDir: @@ -198,11 +198,10 @@ def complete(): # This needs to be printed even if the -quiet option is used if os.path.isfile(os.path.join(tempDir, 'error.txt')): with open(os.path.join(tempDir, 'error.txt'),'rb') as errortext: - sys.stdout.write(os.path.basename(sys.argv[0]) + ': ' + colourWarn + 'Script failed while executing the command: ' + errortext.readline().rstrip() + colourClear + '\n') - sys.stdout.write(os.path.basename(sys.argv[0]) + ': ' + colourWarn + 'For debugging, inspect contents of temporary directory: ' + tempDir + colourClear + '\n') + sys.stderr.write(os.path.basename(sys.argv[0]) + ': ' + colourWarn + 'Script failed while executing the command: ' + errortext.readline().rstrip() + colourClear + '\n') + sys.stderr.write(os.path.basename(sys.argv[0]) + ': ' + colourWarn + 'For debugging, inspect contents of temporary directory: ' + tempDir + colourClear + '\n') else: - sys.stdout.write(os.path.basename(sys.argv[0]) + ': ' + colourPrint + 'Contents of temporary directory kept, location: ' + tempDir + colourClear + '\n') - sys.stdout.flush() + sys.stderr.write(os.path.basename(sys.argv[0]) + ': ' + colourPrint + 'Contents of temporary directory kept, location: ' + tempDir + colourClear + '\n') From 19a6ded966b6d062d83f0bd2511a27bd4efec408 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 15 Sep 2016 15:22:06 +1000 Subject: [PATCH 131/723] Scripts: lib.app.runCommand(): Better detection of last file with -continue Rather than just looking for an instance of a particular string (likely a file name) within the command string, compare the name of the last file indicated by the user against the split command argument strings, additionally checking in case the command call omits the file type extension. --- scripts/lib/runCommand.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/scripts/lib/runCommand.py b/scripts/lib/runCommand.py index ed7394aca1..9ec0109345 100644 --- a/scripts/lib/runCommand.py +++ b/scripts/lib/runCommand.py @@ -18,15 +18,6 @@ def runCommand(cmd, exitOnError=True): # On Windows, strip the .exe's mrtrix_bin_list = [ os.path.splitext(name)[0] for name in os.listdir(mrtrix_bin_path) ] - if lib.app.lastFile: - # Check to see if the last file produced is produced by this command; - # if it is, this will be the last called command that gets skipped - if lib.app.lastFile in cmd: - lib.app.lastFile = '' - if lib.app.verbosity: - sys.stderr.write(lib.app.colourConsole + 'Skipping command:' + lib.app.colourClear + ' ' + cmd + '\n') - return - # Vectorise the command string, preserving anything encased within quotation marks # TODO Use shlex.split()? quotation_split = cmd.split('\"') @@ -42,6 +33,26 @@ def runCommand(cmd, exitOnError=True): else: cmdsplit.extend(item.split()) + if lib.app.lastFile: + # Check to see if the last file produced in the previous script execution is + # intended to be produced by this command; if it is, this will be the last + # command that gets skipped by the -continue option + # It's possible that the file might be defined in a '--option=XXX' style argument + # It's also possible that the filename in the command string has the file extension omitted + for entry in cmdsplit: + if entry.startswith('--') and '=' in entry: + cmdtotest = entry.split('=')[1] + else: + cmdtotest = entry + filetotest = [ lib.app.lastFile, os.path.splitext(lib.app.lastFile)[0] ] + if cmdtotest in filetotest: + debugMessage('Detected last file \'' + lib.app.lastFile + '\' in command \'' + cmd + '\'; this is the last runCommand() call that will be skipped') + lib.app.lastFile = '' + break + if lib.app.verbosity: + sys.stderr.write(lib.app.colourConsole + 'Skipping command:' + lib.app.colourClear + ' ' + cmd + '\n') + return + # For any MRtrix commands, need to insert the nthreads and quiet calls new_cmdsplit = [ ] is_mrtrix_binary = False From 158595c9926bc36bbf96db3313174877b9f262e1 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 15 Sep 2016 15:22:27 +1000 Subject: [PATCH 132/723] dwipreproc: Fixes for -rpe_all functionality --- scripts/dwipreproc | 110 ++++++++++----------------------------------- 1 file changed, 23 insertions(+), 87 deletions(-) diff --git a/scripts/dwipreproc b/scripts/dwipreproc index def9aed94c..28b7e06d0c 100755 --- a/scripts/dwipreproc +++ b/scripts/dwipreproc @@ -177,7 +177,6 @@ if not len(grad) == num_volumes: -<<<<<<< HEAD do_topup = not PE_design == 'None' @@ -282,73 +281,6 @@ if dwi_pe_scheme: dwi_pe_scheme = dwi_pe_manual_scheme # May be used later for triggering volume recombination else: dwi_manual_pe_scheme = None # To guarantee that the scheme stored within the header will be used -======= - # For any non-even axis sizes, crop the first voxel along that dimension - crop_option = '' - for axis, axis_size in enumerate(series_size[:3]): - if int(axis_size)%2: - crop_option += ' -axis ' + str(axis) + ' 1 ' + str(int(axis_size)-1) - - if crop_option: - warnMessage('Input images contain at least one non-even dimension; cropping images for topup / eddy compatibility') - runCommand('mrcrop series.mif series_crop.mif' + crop_option) - delFile('series.mif') - series_path = 'series_crop.mif' - if PE_design == 'Pair': - runCommand('mrcrop pair1.mif pair1_crop.mif' + crop_option) - delFile('pair1.mif') - pair1_path = 'pair1_crop.mif' - runCommand('mrcrop pair2.mif pair2_crop.mif' + crop_option) - delFile('pair2.mif') - pair2_path = 'pair2_crop.mif' - else: # 'All' - runCommand('mrcrop series2.mif series2_crop.mif' + crop_option) - delFile('series2.mif') - series2_path = 'series2_crop.mif' - - -# Convert the input files as necessary for FSL tools -if PE_design == 'None': - runCommand('mrconvert ' + series_path + ' dwi_pre_topup.nii -stride -1,+2,+3,+4') -if PE_design == 'Pair': - runCommand('mrconvert ' + series_path + ' dwi_pre_topup.nii -stride -1,+2,+3,+4') - runCommand('mrcat ' + pair1_path + ' ' + pair2_path + ' - -axis 3 | mrconvert - topup_in.nii -stride -1,+2,+3,+4') -elif PE_design == 'All': - runCommand('dwiextract ' + series_path + ' -bzero pair1.mif') - runCommand('dwiextract ' + series2_path + ' -bzero pair2.mif') - runCommand('mrcat pair1.mif pair2.mif - -axis 3 | mrconvert - topup_in.nii -stride -1,+2,+3,+4') - runCommand('mrconvert ' + series_path + ' dwi1_pre_topup.nii -stride -1,+2,+3,+4') - delFile(series_path) - runCommand('mrconvert ' + series2_path + ' dwi2_pre_topup.nii -stride -1,+2,+3,+4') - delFile(series2_path) - runCommand('mrcat dwi1_pre_topup.nii dwi2_pre_topup.nii dwi_pre_eddy.nii -axis 3') - - -# Calculate how many b=0 volumes are present in each phase-encode direction -rpe_b0_count = [ 1, 0 ] # Always contains integers, _not_ strings -if PE_design == 'Pair' or PE_design == 'All': - rpe_b0_count[1] = 1 - temp = getHeaderInfo(pair1_path, 'size').split() - if len(temp) == 4: - rpe_b0_count[0] = int(temp[3]) - temp = getHeaderInfo(pair2_path, 'size').split() - if len(temp) == 4: - rpe_b0_count[1] = int(temp[3]) - - -# Construct a topup/eddy configuration file -printMessage('Creating phase-encoding configuration file') -config_file = open('config.txt', 'w') -config_line = [ '0', '0', '0', '0.1' ] -if PE_dir[1]: - config_line[PE_dir[0]] = '-1' -else: - config_line[PE_dir[0]] = '1' -for lines in range(0, rpe_b0_count[0]): - config_file.write(' '.join (config_line) + '\n') -if PE_dir[1]: - config_line[PE_dir[0]] = '1' ->>>>>>> origin/master else: # Nothing in the header; rely entirely on user specification if PE_design == 'Header': @@ -599,9 +531,10 @@ else: for axis, line in enumerate(f): bvecs[axis] = line.split() - bvecs_combined_transpose = [ [] * num_volumes/2 ] - bvals_combined = [] * num_volumes/2 - for index, pair in enumerate(volume_pairs): + bvecs_combined_transpose = [ ] + bvals_combined = [ ] + + for pair in volume_pairs: bvec_sum = [ float(bvecs[0][pair[0]]) + float(bvecs[0][pair[1]]), float(bvecs[1][pair[0]]) + float(bvecs[1][pair[1]]), float(bvecs[2][pair[0]]) + float(bvecs[2][pair[1]]) ] @@ -612,18 +545,18 @@ else: new_vec = [ bvec_sum[0]*factor, bvec_sum[1]*factor, bvec_sum[2]*factor ] else: new_vec = [ 0.0, 0.0, 0.0 ] - bvecs_combined_transpose[index] = new_vec - bvals_combined[index] = 0.5 * (grad[pair[0]][3] + grad[pair[1]][3]) + bvecs_combined_transpose.append(new_vec) + bvals_combined.append(0.5 * (grad[pair[0]][3] + grad[pair[1]][3])) with open('bvecs_combined', 'w') as f: for axis in range(0, 3): axis_data = [ ] - for volume in range(0, num_volumes): + for volume in range(0, int(num_volumes/2)): axis_data.append(str(bvecs_combined_transpose[volume][axis])) f.write(' '.join(axis_data) + '\n') with open('bvals_combined', 'w') as f: - f.write(' '.join(bvals_combined)) + f.write(' '.join( [ str(b) for b in bvals_combined ] )) @@ -649,9 +582,12 @@ else: # The jacobian image may be different for any particular volume pair # The appropriate PE directions and total readout times can be acquired from the eddy-style config/index files # eddy_config.txt and eddy_indices.txt - - eddy_config = [ [ float(f) for f in line ] for line in open('eddy_config.txt', 'r').read().split('\n') ] - eddy_indices = [ int(i) for i in open('eddy_indices.txt', 'r').read() ] + + eddy_config = [ [ float(f) for f in line.split() ] for line in open('eddy_config.txt', 'r').read().split('\n')[:-1] ] + eddy_indices = [ int(i) for i in open('eddy_indices.txt', 'r').read().split() ] + + print (eddy_config) + print (eddy_indices) # This section derives, for each phase encoding configuration present, the 'weight' to be applied # to the image during volume recombination, which is based on the Jacobian of the field in the @@ -659,29 +595,29 @@ else: for index, config in enumerate(eddy_config): pe_axis = [ i for i, e in enumerate(config[0:3]) if e != 0][0] sign_multiplier = ' -1.0 -mult' if config[pe_axis] < 0 else '' - field_derivative_path = 'field_deriv_pe_' + str(index) + 'mif' + field_derivative_path = 'field_deriv_pe_' + str(index+1) + '.mif' runCommand('mrcalc ' + field_map_image + ' ' + str(config[3]) + ' -mult' + sign_multiplier + ' - | mrfilter - gradient - | mrconvert - ' + field_derivative_path + ' -coord 3 ' + str(pe_axis) + ' -axes 0,1,2') - jacobian_path = 'jacobian_' + str(index) + '.mif' + jacobian_path = 'jacobian_' + str(index+1) + '.mif' runCommand('mrcalc 1.0 ' + field_derivative_path + ' -add 0.0 -max ' + jacobian_path) delFile(field_derivative_path) - runCommand('mrcalc ' + jacobian_path + ' ' + jacobian_path + ' -mult weight' + str(index) + '.mif') + runCommand('mrcalc ' + jacobian_path + ' ' + jacobian_path + ' -mult weight' + str(index+1) + '.mif') delFile(jacobian_path) # This section extracts the two volumes corresponding to each reversed phase-encoded volume pair, and # derives a single image volume based on the recombination equation combined_image_list = [ ] for index, volumes in enumerate(volume_pairs): - config_lines = [ eddy_config[i] for i in volumes ] - runCommand('mrconvert dwi_post_eddy' + fsl_suffix + ' volume0.mif -coord 3 ' + volumes[0]) - runCommand('mrconvert dwi_post_eddy' + fsl_suffix + ' volume1.mif -coord 3 ' + volumes[1]) + pe_indices = [ eddy_indices[i] for i in volumes ] + runCommand('mrconvert dwi_post_eddy' + fsl_suffix + ' volume0.mif -coord 3 ' + str(volumes[0])) + runCommand('mrconvert dwi_post_eddy' + fsl_suffix + ' volume1.mif -coord 3 ' + str(volumes[1])) # Volume recombination equation described in Skare and Bammer 2010 - runCommand('mrcalc volume0.mif weight' + str(volumes[0]) + '.mif -mult volumes1.mif weight' + str(volumes[1]) + '.mif -mult -add weight' + str(volumes[0]) + '.mif weight' + str(volumes[1]) + '.mif -add -divide 0.0 -max combined' + str(index) + '.mif') + runCommand('mrcalc volume0.mif weight' + str(pe_indices[0]) + '.mif -mult volume1.mif weight' + str(pe_indices[1]) + '.mif -mult -add weight' + str(pe_indices[0]) + '.mif weight' + str(pe_indices[1]) + '.mif -add -divide 0.0 -max combined' + str(index) + '.mif') combined_image_list.append('combined' + str(index) + '.mif') os.unlink('volume0.mif') os.unlink('volume1.mif') - for index in range(0, len(eddy_config): - delFile('weight' + str(index) + '.mif') + for index in range(0, len(eddy_config)): + delFile('weight' + str(index+1) + '.mif') # Finally the recombined volumes must be concatenated to produce the resulting image series runCommand('mrcat ' + ' '.join(combined_image_list) + ' - -axis 3 | mrconvert - result.mif -fslgrad bvecs_combined bvals_combined' + stride_option) From 1647c0644862286a3375877ac7e7cd8241fb3b5d Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 15 Sep 2016 15:23:49 +1000 Subject: [PATCH 133/723] dwipreproc: Remove debugging printouts --- scripts/dwipreproc | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/dwipreproc b/scripts/dwipreproc index 28b7e06d0c..dd38fdc8f3 100755 --- a/scripts/dwipreproc +++ b/scripts/dwipreproc @@ -533,7 +533,7 @@ else: bvecs_combined_transpose = [ ] bvals_combined = [ ] - + for pair in volume_pairs: bvec_sum = [ float(bvecs[0][pair[0]]) + float(bvecs[0][pair[1]]), float(bvecs[1][pair[0]]) + float(bvecs[1][pair[1]]), @@ -585,9 +585,6 @@ else: eddy_config = [ [ float(f) for f in line.split() ] for line in open('eddy_config.txt', 'r').read().split('\n')[:-1] ] eddy_indices = [ int(i) for i in open('eddy_indices.txt', 'r').read().split() ] - - print (eddy_config) - print (eddy_indices) # This section derives, for each phase encoding configuration present, the 'weight' to be applied # to the image during volume recombination, which is based on the Jacobian of the field in the From 68627f8a342f4384a95103f0a7073933845270d7 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 15 Sep 2016 16:42:27 +1000 Subject: [PATCH 134/723] Fixed issue in warp2metric --- cmd/warp2metric.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmd/warp2metric.cpp b/cmd/warp2metric.cpp index f8570b2b39..79c013ecc7 100644 --- a/cmd/warp2metric.cpp +++ b/cmd/warp2metric.cpp @@ -89,11 +89,8 @@ void run () for (auto l = Loop (fixel_template_index, 0, 3) (fixel_template_index); l; ++l) num_fixels += fixel_template_index.value(); - Header output_header (fixel_template_index); - output_header.ndim() = 3; - output_header.size(0) = num_fixels; + Header output_header (fixel_template_directions); output_header.size(1) = 1; - output_header.size(2) = 1; output_header.datatype() = DataType::Float32; output_header.datatype().set_byte_order_native(); fc_output_data = Image::create (Path::join(output_fixel_folder, opt[0][2]), output_header); From 43f3888bd50537965abcfac93960baccd0fbcc78 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 15 Sep 2016 16:43:16 +1000 Subject: [PATCH 135/723] Debugging fixelcfestats --- cmd/fixel2voxel.cpp | 4 ++-- cmd/fixelcfestats.cpp | 34 ++++++++++++++++++++++++++-------- lib/fixel_format/helpers.h | 21 ++++++++++----------- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/cmd/fixel2voxel.cpp b/cmd/fixel2voxel.cpp index 50ac55c7ae..9f4be28e60 100644 --- a/cmd/fixel2voxel.cpp +++ b/cmd/fixel2voxel.cpp @@ -475,8 +475,8 @@ void run () 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), in_index_image, out); break; - case 9: loop.run (SF (in_data), in_index_image, out); break; + case 8: loop.run (Complexity (in_data), in_index_image, out); break; + case 9: loop.run (SF (in_data), in_index_image, out); break; case 10: loop.run (DEC_unit (in_data, in_vol, in_directions), in_index_image, out); break; case 11: loop.run (DEC_scaled (in_data, in_vol, in_directions), in_index_image, out); break; case 12: loop.run (SplitData (in_data), in_index_image, out); break; diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index d145b4775b..7b290116f8 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -152,32 +152,45 @@ void run() { Header index_header = FixelFormat::find_index_header (input_fixel_folder); auto index_image = index_header.get_image(); uint32_t num_fixels = std::stoul (index_image.keyval().at(FixelFormat::n_fixels_key)); + CONSOLE ("number of fixels: " + str(num_fixels)); std::vector positions (num_fixels); std::vector directions (num_fixels); + const std::string output_fixel_folder = argument[5]; + FixelFormat::check_fixel_folder (output_fixel_folder, true); + { + TRACE; + // copy index to output folder TODO why so long to copy here?? + auto output_index_image = Image::create (Path::join (output_fixel_folder, Path::basename (index_image.name())), index_image); + threaded_copy_with_progress_message ("copying fixel index into output folder", index_image, output_index_image); auto directions_data = FixelFormat::find_directions_header (input_fixel_folder, index_image).get_image().with_direct_io(); + auto output_directions_data = Image::create(Path::join (output_fixel_folder, Path::basename (directions_data.name())), directions_data); + threaded_copy_with_progress_message ("copying fixel directions into output folder", directions_data, output_directions_data); + TRACE; + // Load template fixel directions Transform image_transform (index_image); - for (auto i = Loop (index_image)(index_image); i; ++i) { + for (auto i = Loop ("loading template fixel directions and positions", index_image, 0, 3)(index_image); i; ++i) { const Eigen::Vector3 vox ((default_type)index_image.index(0), (default_type)index_image.index(1), (default_type)index_image.index(2)); index_image.index(3) = 1; uint32_t offset = index_image.value(); size_t fixel_index = 0; - for (auto f = FixelFormat::FixelLoop (index_image) (directions_data); f; ++f, ++fixel_index) { + for (auto f = FixelFormat::FixelLoop (index_image) (directions_data, output_directions_data); f; ++f, ++fixel_index) { directions[offset + fixel_index] = directions_data.row(1).cast(); positions[offset + fixel_index] = image_transform.voxel2scanner * vox; } } } - CONSOLE ("number of fixels: " + str(num_fixels)); // Read identifiers and check files exist std::vector identifiers; Header header; { + TRACE; + ProgressBar progress ("validating input files..."); std::ifstream ifs (argument[1].c_str()); std::string temp; while (getline (ifs, temp)) { @@ -190,28 +203,30 @@ void run() { header = Header::open (filename); FixelFormat::fixels_match (index_header, header); identifiers.push_back (filename); + progress++; } } + TRACE; + // Load design matrix: const matrix_type design = load_matrix (argument[2]); if (design.rows() != (ssize_t)identifiers.size()) throw Exception ("number of input files does not match number of rows in design matrix"); + TRACE; // Load contrast matrix: const matrix_type contrast = load_matrix (argument[3]); + TRACE; + if (contrast.cols() != design.cols()) throw Exception ("the number of contrasts does not equal the number of columns in the design matrix"); if (contrast.rows() > 1) throw Exception ("only a single contrast vector (defined as a row) is currently supported"); - // Copy index and directions over to output folder - const std::string output_fixel_folder = argument[5]; - FixelFormat::copy_index_file (input_fixel_folder, output_fixel_folder); - FixelFormat::copy_directions_file (input_fixel_folder, output_fixel_folder); - + TRACE; // Compute fixel-fixel connectivity std::vector > connectivity_matrix (num_fixels); @@ -338,6 +353,9 @@ void run() { { ProgressBar progress ("outputting beta coefficients, effect size and standard deviation"); auto temp = Math::Stats::GLM::solve_betas (data, design); + + std::cout << temp.rows() << ", " << temp.cols(); + for (ssize_t i = 0; i < contrast.cols(); ++i) { write_fixel_output (Path::join (output_fixel_folder, "beta" + str(i) + ".mif"), temp.row(i), output_header); ++progress; diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index c1eeb2667a..267668a0c2 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -196,34 +196,33 @@ namespace MR } - //! Copy a file from one fixel folder into another. If the output folder already contains the same file - //! then it is checked to make sure it's the same as the input (based on the number of fixels it contains) + //! Copy a file from one fixel folder into another. inline void copy_fixel_file (const std::string& input_file_path, const std::string &output_folder) { check_fixel_folder (output_folder, true); std::string output_path = Path::join (output_folder, Path::basename (input_file_path)); Header input_header = Header::open (input_file_path); auto input_image = input_header.get_image(); - auto output_directions_image = Image::create (output_path, input_header); - threaded_copy (input_image, output_directions_image); + auto output_image = Image::create (output_path, input_header); + threaded_copy (input_image, output_image); } - //! Copy the index file from one fixel folder into another. When check_existing_output - //! is true, it will check if existing output exists and NOT overwrite as long as the number of fixels in the file is the same + //! Copy the index file from one fixel folder into another inline void copy_index_file (const std::string &input_folder, const std::string &output_folder) { Header input_header = FixelFormat::find_index_header (input_folder); - copy_fixel_file (input_header.name(), output_folder); + check_fixel_folder (output_folder, true); + auto output_image = Image::create (Path::join (output_folder, Path::basename (input_header.name())), input_header); + auto input_image = input_header.get_image(); + threaded_copy (input_image, output_image); } - //! Copy the directions file from one fixel folder into another. When check_existing_output - //! is true, it will check if existing output exists and NOT overwrite as long as the number of fixels in the file is the same + //! Copy the directions file from one fixel folder into another. inline void copy_directions_file (const std::string &input_folder, const std::string &output_folder) { Header input_header = FixelFormat::find_directions_header (input_folder, FixelFormat::find_index_header (input_folder)); copy_fixel_file (input_header.name(), output_folder); } - //! Copy all data files in a fixel folder into another folder. Data files do not include the index or directions file. When check_existing_output - //! is true, it will check if existing outputs exist and NOT overwrite as long as the number of fixels in the file is the same + //! Copy all data files in a fixel folder into another folder. Data files do not include the index or directions file. inline void copy_all_data_files (const std::string &input_folder, const std::string &output_folder) { for (auto& input_header : FixelFormat::find_data_headers (input_folder, FixelFormat::find_index_header (input_folder))) copy_fixel_file (input_header.name(), output_folder); From 92cc8111acf5487f1283288eeaa5db35e285dea3 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 19 Sep 2016 12:10:15 +1000 Subject: [PATCH 136/723] Scripts: Fix printing of failed command --- docs/reference/scripts/dwi2response.rst | 4 +++- scripts/lib/app.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/reference/scripts/dwi2response.rst b/docs/reference/scripts/dwi2response.rst index a19ffc132c..66b20930d9 100644 --- a/docs/reference/scripts/dwi2response.rst +++ b/docs/reference/scripts/dwi2response.rst @@ -68,7 +68,9 @@ Standard options - **-quiet** Suppress all console output during script execution -- **-verbose** Display additional information for every command invoked +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output References ^^^^^^^^^^ diff --git a/scripts/lib/app.py b/scripts/lib/app.py index e5d073bcaf..9101f83e34 100644 --- a/scripts/lib/app.py +++ b/scripts/lib/app.py @@ -197,7 +197,7 @@ def complete(): elif tempDir: # This needs to be printed even if the -quiet option is used if os.path.isfile(os.path.join(tempDir, 'error.txt')): - with open(os.path.join(tempDir, 'error.txt'),'rb') as errortext: + with open(os.path.join(tempDir, 'error.txt'), 'r') as errortext: sys.stderr.write(os.path.basename(sys.argv[0]) + ': ' + colourWarn + 'Script failed while executing the command: ' + errortext.readline().rstrip() + colourClear + '\n') sys.stderr.write(os.path.basename(sys.argv[0]) + ': ' + colourWarn + 'For debugging, inspect contents of temporary directory: ' + tempDir + colourClear + '\n') else: From a128c598ffe145e255d176a152d868404e000726 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Mon, 19 Sep 2016 12:22:30 +1000 Subject: [PATCH 137/723] Fix bug in new fixelreorient --- cmd/fixelreorient.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/fixelreorient.cpp b/cmd/fixelreorient.cpp index b39a11ce8a..8ed794cb8f 100644 --- a/cmd/fixelreorient.cpp +++ b/cmd/fixelreorient.cpp @@ -85,6 +85,7 @@ void run () input_directions_image.index(0) = index + f; output_directions_image.index(0) = index + f; output_directions_image.row(1) = transform * input_directions_image.row(1); + output_directions_image.row(1).normalize(); } } } From e3a91e8ba615c093a438487f22cbd650e21adcc5 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Mon, 19 Sep 2016 12:29:18 +1000 Subject: [PATCH 138/723] Make fixelcorrespondence robust to directions that are not unit vectors --- cmd/fixelcorrespondence.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/fixelcorrespondence.cpp b/cmd/fixelcorrespondence.cpp index 19142ca656..b7cfd31f10 100644 --- a/cmd/fixelcorrespondence.cpp +++ b/cmd/fixelcorrespondence.cpp @@ -38,7 +38,7 @@ void usage () ARGUMENTS + Argument ("subject_data", "the input subject fixel data file. This should be a file inside the fixel folder").type_image_in () + Argument ("template_folder", "the input template fixel folder.").type_image_in () - + Argument ("output_folder", "the output fixel folder. Any existing index file in the output folder will be checked that it matches the expected output.").type_text() + + Argument ("output_folder", "the output fixel folder.").type_text() + Argument ("output_data", "the name of the output fixel data file. This will be placed in the output fixel folder").type_image_out (); OPTIONS @@ -96,7 +96,12 @@ void run () template_directions.index(0) = template_offset + t; for (size_t s = 0; s < nfixels_subject; ++s) { subject_directions.index(0) = subject_offset + s; - float dp = std::abs (template_directions.row(1).dot (subject_directions.row(1))); + + Eigen::Vector3f templatedir = template_directions.row(1); + templatedir.normalize(); + Eigen::Vector3f subjectdir = subject_directions.row(1); + subjectdir.normalize(); + float dp = std::abs (templatedir.dot (subjectdir)); if (dp > largest_dp) { largest_dp = dp; index_of_closest_fixel = s; From 055b71f0258e041e0a7dafec37f83fc6545871ce Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 19 Sep 2016 13:39:24 +1000 Subject: [PATCH 139/723] Scripts: Fix help page printout for certain option types --- scripts/lib/cmdlineParser.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/lib/cmdlineParser.py b/scripts/lib/cmdlineParser.py index 4116fc27a7..7745cc7d6a 100644 --- a/scripts/lib/cmdlineParser.py +++ b/scripts/lib/cmdlineParser.py @@ -164,7 +164,12 @@ def underline(text): else: s += option.metavar elif option.nargs: - s += (' ' + option.dest.upper())*option.nargs + if isinstance(option.nargs, int): + s += (' ' + option.dest.upper())*option.nargs + elif option.nargs == '+' or option.nargs == '*': + s += ' ' + elif option.nargs == '?': + s += ' ' elif option.type is not None: s += ' ' + option.type.__name__.upper() elif option.default is None: From 041e76045b40e86f2d8be20f45334a3f274bbb83 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 20 Sep 2016 10:30:19 +1000 Subject: [PATCH 140/723] Fix reported error on absent gradient table By running check_DW_scheme() before normalise_grad(), the first terminal error in the case of an absent diffusion gradient table will be 'no diffusion gradient table found' rather than 'gradient table has invalid dimensions'. --- src/dwi/gradient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dwi/gradient.cpp b/src/dwi/gradient.cpp index 801d104b4c..5485c41cb0 100644 --- a/src/dwi/gradient.cpp +++ b/src/dwi/gradient.cpp @@ -236,8 +236,8 @@ namespace MR scale_bvalue_by_G_squared (grad); try { - normalise_grad (grad); check_DW_scheme (header, grad); + normalise_grad (grad); } catch (Exception& e) { if (!nofail) From d276f80bb91bf9053b766d30e9e2c5f4b60039a4 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 20 Sep 2016 13:16:01 +1000 Subject: [PATCH 141/723] Better detection of no gradient table in dwi2response - Check presence of diffusion gradient table in header before dwiextract is called, rather than after. - Prevent function set_DW_scheme() from creating an empty keyval entry. - Call get_DW_scheme() rather than parse_DW_scheme() in mrinfo for compatibility with GradImportOptions(). --- cmd/mrinfo.cpp | 2 +- scripts/dwi2response | 11 ++--------- src/dwi/gradient.h | 4 ++++ 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/cmd/mrinfo.cpp b/cmd/mrinfo.cpp index af9147018a..407e517f98 100644 --- a/cmd/mrinfo.cpp +++ b/cmd/mrinfo.cpp @@ -243,7 +243,7 @@ void run () if (offset) std::cout << header.intensity_offset() << "\n"; if (multiplier) std::cout << header.intensity_scale() << "\n"; if (transform) print_transform (header); - if (dwgrad) std::cout << DWI::parse_DW_scheme (header) << "\n"; + if (dwgrad) std::cout << DWI::get_DW_scheme (header) << "\n"; if (shells || shellcounts) print_shells (header, shells, shellcounts); if (petable) std::cout << PhaseEncoding::get_scheme (header) << "\n"; diff --git a/scripts/dwi2response b/scripts/dwi2response index f545e92f16..6b55802816 100755 --- a/scripts/dwi2response +++ b/scripts/dwi2response @@ -80,6 +80,8 @@ if lib.app.args.grad: grad_import_option = ' -grad ' + getUserPath(lib.app.args.grad, True) elif lib.app.args.fslgrad: grad_import_option = ' -fslgrad ' + getUserPath(lib.app.args.fslgrad[0], True) + ' ' + getUserPath(lib.app.args.fslgrad[1], True) +elif not getHeaderInfo(getUserPath(lib.app.args.input, False), 'dwgrad'): + errorMessage('Script requires diffusion gradient table: either in image header, or using -grad / -fslgrad option') lib.app.makeTempDir() @@ -94,15 +96,6 @@ if lib.app.args.mask: algorithm.getInputFiles() lib.app.gotoTempDir() - - -# Make sure it's actually a DWI that's been passed -size = getHeaderInfo('dwi.mif', 'size').split() -if len(size) != 4: - errorMessage('Input image must be a 4D image') -DW_scheme = getHeaderInfo('dwi.mif', 'dwgrad').split('\n') -if len(DW_scheme) != int(size[3]): - errorMessage('Input image does not contain valid DW gradient scheme') # Generate a brain mask (if necessary) diff --git a/src/dwi/gradient.h b/src/dwi/gradient.h index 3b06b07a4d..c613c29638 100644 --- a/src/dwi/gradient.h +++ b/src/dwi/gradient.h @@ -139,6 +139,10 @@ namespace MR template void set_DW_scheme (Header& header, const MatrixType& G) { + if (!G.rows()) { + header.keyval().erase ("dw_scheme"); + return; + } std::string dw_scheme; for (ssize_t row = 0; row < G.rows(); ++row) { std::string line; From 7131e2f13232798341c4f8eb5a69bdc5899ddb53 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Tue, 20 Sep 2016 16:49:41 +1000 Subject: [PATCH 142/723] Ported dwi2response to new fixel image format --- scripts/lib/delFile.py | 10 ++++++++++ scripts/src/dwi2response/tournier.py | 8 ++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/scripts/lib/delFile.py b/scripts/lib/delFile.py index e3ba935a5e..71ffcb3e3e 100644 --- a/scripts/lib/delFile.py +++ b/scripts/lib/delFile.py @@ -7,3 +7,13 @@ def delFile(path): printMessage('Deleting file: ' + path) os.remove(path) + +def delFolder(path): + import lib.app, os, shutil + from lib.printMessage import printMessage + if not lib.app.cleanup: + return + if lib.app.verbosity > 1: + printMessage('Deleting folder: ' + path) + if os.path.exists(path): + shutil.rmtree(path) diff --git a/scripts/src/dwi2response/tournier.py b/scripts/src/dwi2response/tournier.py index 07b06e2cc2..105f79d483 100644 --- a/scripts/src/dwi2response/tournier.py +++ b/scripts/src/dwi2response/tournier.py @@ -64,16 +64,16 @@ def execute(): # Get amplitudes of two largest peaks, and direction of largest # TODO Speed-test fod2fixel against sh2peaks # TODO Add maximum number of fixels per voxel option to fod2fixel? - runCommand('fod2fixel ' + prefix + 'FOD.mif -peak ' + prefix + 'peaks.msf -mask ' + mask_in_path + ' -fmls_no_thresholds') + runCommand('fod2fixel ' + prefix + 'FOD.mif ' + prefix + 'fixel -peak peaks.mif -mask ' + mask_in_path + ' -fmls_no_thresholds') delFile(prefix + 'FOD.mif') if iteration: delFile(mask_in_path) - runCommand('fixel2voxel ' + prefix + 'peaks.msf split_value ' + prefix + 'amps.mif') + runCommand('fixel2voxel ' + prefix + 'fixel/peaks.mif split_data ' + prefix + 'amps.mif') runCommand('mrconvert ' + prefix + 'amps.mif ' + prefix + 'first_peaks.mif -coord 3 0 -axes 0,1,2') runCommand('mrconvert ' + prefix + 'amps.mif ' + prefix + 'second_peaks.mif -coord 3 1 -axes 0,1,2') delFile(prefix + 'amps.mif') - runCommand('fixel2voxel ' + prefix + 'peaks.msf split_dir ' + prefix + 'all_dirs.mif') - delFile(prefix + 'peaks.msf') + runCommand('fixel2voxel ' + prefix + 'fixel/directions.mif split_dir ' + prefix + 'all_dirs.mif') + delfolder(prefix + 'fixel') runCommand('mrconvert ' + prefix + 'all_dirs.mif ' + prefix + 'first_dir.mif -coord 3 0:2') delFile(prefix + 'all_dirs.mif') # Calculate the 'cost function' Donald derived for selecting single-fibre voxels From 6b927d78d7800402d0341aa2982abd455cf98561 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Tue, 20 Sep 2016 16:50:09 +1000 Subject: [PATCH 143/723] Debugged fixelcfestats after port to new fixel format --- cmd/fixelcfestats.cpp | 65 ++++++++----------- .../commands/fixelcorrespondence.rst | 2 +- docs/reference/commands_list.rst | 2 + src/stats/cfe.cpp | 15 +++-- src/stats/cfe.h | 4 +- 5 files changed, 39 insertions(+), 49 deletions(-) diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 7b290116f8..d5d9c3e95f 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -123,10 +123,10 @@ void write_fixel_output (const std::string& filename, const VectorType& data, const Header& header) { - assert (data.size() == header.size(2)); + assert (data.size() == header.size(0)); auto output = Image::create (filename, header); for (uint32_t i = 0; i < data.size(); ++i) { - output.index(2) = i; + output.index(0) = i; output.value() = data[i]; } } @@ -151,6 +151,8 @@ void run() { const std::string input_fixel_folder = argument[0]; Header index_header = FixelFormat::find_index_header (input_fixel_folder); auto index_image = index_header.get_image(); + + uint32_t num_fixels = std::stoul (index_image.keyval().at(FixelFormat::n_fixels_key)); CONSOLE ("number of fixels: " + str(num_fixels)); @@ -161,15 +163,13 @@ void run() { FixelFormat::check_fixel_folder (output_fixel_folder, true); { - TRACE; // copy index to output folder TODO why so long to copy here?? auto output_index_image = Image::create (Path::join (output_fixel_folder, Path::basename (index_image.name())), index_image); - threaded_copy_with_progress_message ("copying fixel index into output folder", index_image, output_index_image); + threaded_copy_with_progress_message ("copying fixel index into output folder", index_image, output_index_image, 0, std::numeric_limits::max(), 2); auto directions_data = FixelFormat::find_directions_header (input_fixel_folder, index_image).get_image().with_direct_io(); auto output_directions_data = Image::create(Path::join (output_fixel_folder, Path::basename (directions_data.name())), directions_data); threaded_copy_with_progress_message ("copying fixel directions into output folder", directions_data, output_directions_data); - TRACE; // 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) { @@ -183,13 +183,10 @@ void run() { } } } - - // Read identifiers and check files exist std::vector identifiers; Header header; { - TRACE; ProgressBar progress ("validating input files..."); std::ifstream ifs (argument[1].c_str()); std::string temp; @@ -207,33 +204,25 @@ void run() { } } - TRACE; - // Load design matrix: const matrix_type design = load_matrix (argument[2]); if (design.rows() != (ssize_t)identifiers.size()) throw Exception ("number of input files does not match number of rows in design matrix"); - TRACE; - // Load contrast matrix: const matrix_type contrast = load_matrix (argument[3]); - TRACE; - if (contrast.cols() != design.cols()) throw Exception ("the number of contrasts does not equal the number of columns in the design matrix"); if (contrast.rows() > 1) throw Exception ("only a single contrast vector (defined as a row) is currently supported"); - TRACE; - // Compute fixel-fixel connectivity std::vector > connectivity_matrix (num_fixels); std::vector fixel_TDI (num_fixels, 0.0); const std::string track_filename = argument[4]; 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"]); if (!num_tracks) @@ -273,7 +262,7 @@ void run() { auto it = connectivity_matrix[fixel].begin(); while (it != connectivity_matrix[fixel].end()) { - const value_type connectivity = it->second.value / value_type (fixel_TDI[fixel]); + const connectivity_value_type connectivity = it->second.value / connectivity_value_type (fixel_TDI[fixel]); if (connectivity < connectivity_threshold) { connectivity_matrix[fixel].erase (it++); } else { @@ -282,7 +271,8 @@ void run() { Math::pow2 (positions[fixel][1] - positions[it->first][1]) + Math::pow2 (positions[fixel][2] - positions[it->first][2])); const float smoothing_weight = connectivity * gaussian_const1 * std::exp (-std::pow (distance, 2) / gaussian_const2); - smoothing_weights[fixel].insert (std::pair (it->first, smoothing_weight)); + if (smoothing_weight > 0.01) + smoothing_weights[fixel].insert (std::pair (it->first, smoothing_weight)); } // Here we pre-exponentiate each connectivity value by C it->second.value = std::pow (connectivity, cfe_c); @@ -295,18 +285,28 @@ void run() { // Normalise smoothing weights value_type sum = 0.0; - for (auto it = smoothing_weights[fixel].begin(); it != smoothing_weights[fixel].end(); ++it) - sum += it->second; + for (auto smooth_it = smoothing_weights[fixel].begin(); smooth_it != smoothing_weights[fixel].end(); ++smooth_it) { + sum += smooth_it->second; + } value_type norm_factor = 1.0 / sum; - for (auto it = smoothing_weights[fixel].begin(); it != smoothing_weights[fixel].end(); ++it) { - it->second *= norm_factor; - if (it->second < 0.005) - smoothing_weights[fixel].erase (it++); + for (auto smooth_it = smoothing_weights[fixel].begin(); smooth_it != smoothing_weights[fixel].end(); ++smooth_it) { + smooth_it->second *= norm_factor; } progress++; } } + Header output_header (header); + output_header.keyval()["num permutations"] = str(num_perms); + output_header.keyval()["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()["angular threshold"] = str(angular_threshold); + output_header.keyval()["connectivity threshold"] = str(connectivity_threshold); + output_header.keyval()["smoothing FWHM"] = str(smooth_std_dev * 2.3548); + + // Load input data matrix_type data (num_fixels, identifiers.size()); { @@ -332,6 +332,7 @@ void run() { value += subject_data_vector[it->first] * it->second; data (fixel, subject) = value; } + progress++; } } @@ -339,23 +340,10 @@ void run() { if (!data.allFinite()) throw Exception ("input data contains non-finite value(s)"); - - Header output_header (header); - output_header.keyval()["num permutations"] = str(num_perms); - output_header.keyval()["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()["angular threshold"] = str(angular_threshold); - output_header.keyval()["connectivity threshold"] = str(connectivity_threshold); - output_header.keyval()["smoothing FWHM"] = str(smooth_std_dev * 2.3548); - { ProgressBar progress ("outputting beta coefficients, effect size and standard deviation"); auto temp = Math::Stats::GLM::solve_betas (data, design); - std::cout << temp.rows() << ", " << temp.cols(); - for (ssize_t i = 0; i < contrast.cols(); ++i) { write_fixel_output (Path::join (output_fixel_folder, "beta" + str(i) + ".mif"), temp.row(i), output_header); ++progress; @@ -373,7 +361,6 @@ void run() { cfe_integrator.reset (new Stats::CFE::Enhancer (connectivity_matrix, cfe_dh, cfe_e, cfe_h)); vector_type empirical_cfe_statistic; - // If performing non-stationarity adjustment we need to pre-compute the empirical CFE statistic if (do_nonstationary_adjustment) { Stats::PermTest::precompute_empirical_stat (glm_ttest, cfe_integrator, nperms_nonstationary, empirical_cfe_statistic); diff --git a/docs/reference/commands/fixelcorrespondence.rst b/docs/reference/commands/fixelcorrespondence.rst index 7b4f9c5912..fb44baf47e 100644 --- a/docs/reference/commands/fixelcorrespondence.rst +++ b/docs/reference/commands/fixelcorrespondence.rst @@ -12,7 +12,7 @@ Synopsis - *subject_data*: the input subject fixel data file. This should be a file inside the fixel folder - *template_folder*: the input template fixel folder. -- *output_folder*: the output fixel folder. Any existing index file in the output folder will be checked that it matches the expected output. +- *output_folder*: the output fixel folder. - *output_data*: the name of the output fixel data file. This will be placed in the output fixel folder Description diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index 00bfc2aaed..6409d79448 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -17,6 +17,8 @@ List of MRtrix3 commands commands/5ttedit + commands/addnoise + commands/afdconnectivity commands/amp2sh diff --git a/src/stats/cfe.cpp b/src/stats/cfe.cpp index 3bf0051da0..771449dd15 100644 --- a/src/stats/cfe.cpp +++ b/src/stats/cfe.cpp @@ -33,22 +33,23 @@ namespace MR fixel_directions (fixel_directions), fixel_TDI (fixel_TDI), connectivity_matrix (connectivity_matrix), - angular_threshold_dp (angular_threshold * (Math::pi/180.0)) { } + angular_threshold_dp (std::cos (angular_threshold * (Math::pi/180.0))) { } - bool TrackProcessor::operator () (const SetVoxelDir& in) + bool TrackProcessor::operator() (const SetVoxelDir& in) { // For each voxel tract tangent, assign to a fixel - std::vector tract_fixel_indices; + std::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; - uint32_t first_index = fixel_indexer.value(); - if (first_index >= 0) { + uint32_t num_fibres = fixel_indexer.value(); + if (num_fibres > 0) { fixel_indexer.index(3) = 1; - uint32_t last_index = first_index + fixel_indexer.value(); - uint32_t closest_fixel_index = -1; + uint32_t first_index = fixel_indexer.value(); + uint32_t last_index = first_index + num_fibres; + uint32_t closest_fixel_index = 0; value_type largest_dp = 0.0; const direction_type dir (i->get_dir().normalized()); for (uint32_t j = first_index; j < last_index; ++j) { diff --git a/src/stats/cfe.h b/src/stats/cfe.h index d7a526269b..af03c4cc5b 100644 --- a/src/stats/cfe.h +++ b/src/stats/cfe.h @@ -33,7 +33,7 @@ namespace MR typedef Math::Stats::value_type value_type; typedef Math::Stats::vector_type vector_type; typedef float connectivity_value_type; - typedef Eigen::Matrix direction_type; + typedef Eigen::Matrix direction_type; typedef Eigen::Array connectivity_vector_type; typedef DWI::Tractography::Mapping::SetVoxelDir SetVoxelDir; @@ -73,7 +73,7 @@ namespace MR const std::vector& fixel_directions; std::vector& fixel_TDI; std::vector >& connectivity_matrix; - default_type angular_threshold_dp; + const value_type angular_threshold_dp; }; From 98747b6381a27a0db6045b566c1c8b0c051122e7 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Tue, 20 Sep 2016 17:17:02 +1000 Subject: [PATCH 144/723] make fixel index file search more robust --- cmd/fixelcfestats.cpp | 1 - lib/fixel_format/helpers.h | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index d5d9c3e95f..1a5a58406f 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -163,7 +163,6 @@ void run() { FixelFormat::check_fixel_folder (output_fixel_folder, true); { - // copy index to output folder TODO why so long to copy here?? auto output_index_image = Image::create (Path::join (output_fixel_folder, Path::basename (index_image.name())), index_image); threaded_copy_with_progress_message ("copying fixel index into output folder", index_image, output_index_image, 0, std::numeric_limits::max(), 2); auto directions_data = FixelFormat::find_directions_header (input_fixel_folder, index_image).get_image().with_direct_io(); diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index 267668a0c2..1ed1534acb 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -33,34 +33,34 @@ namespace MR { inline bool is_index_image (const Header& in) { - return in.keyval ().count (n_fixels_key); + return (in.size(2) != 1) && in.keyval().count (n_fixels_key); } inline void check_index_image (const Header& in) { - if (!is_index_image (in)) + if (!is_index_image(in)) throw InvalidImageException (in.name () + " is not a valid fixel index image. Header key " + n_fixels_key + " not found"); } inline bool is_data_file (const Header& in) { - return in.ndim () == 3 && in.size (2) == 1; + return in.ndim() == 3 && in.size(2) == 1; } inline bool is_directions_file (const Header& in) { std::string basename (Path::basename (in.name())); - return in.ndim () == 3 && in.size (1) == 3 && in.size (2) == 1 && (basename.substr(0, basename.find_last_of(".")) == "directions"); + return in.ndim() == 3 && in.size(1) == 3 && in.size(2) == 1 && (basename.substr(0, basename.find_last_of(".")) == "directions"); } inline void check_data_file (const Header& in) { if (!is_data_file (in)) - throw InvalidImageException (in.name () + " is not a valid fixel data file. Expected a 3-dimensional image of size n x m x 1"); + throw InvalidImageException (in.name() + " is not a valid fixel data file. Expected a 3-dimensional image of size n x m x 1"); } inline std::string get_fixel_folder (const std::string& fixel_file) { From aa612990137e29706e6fca28e9844364f3799948 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 21 Sep 2016 11:15:52 +1000 Subject: [PATCH 145/723] Further alteration of system signal handling On Windows, use the semi-depreciated signal() function rather than sigaction(). This also means that testing signal handling as part of the configure script is no longer necessary. --- configure | 26 ----------------- lib/signal_handler.cpp | 63 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 55 insertions(+), 34 deletions(-) diff --git a/configure b/configure index 28e5fa0d3e..3edf0a5ae2 100755 --- a/configure +++ b/configure @@ -1080,32 +1080,6 @@ if R_module: -report ('Checking ability to handle system signals: ') -try: - handle_signals = compile(''' -#include -#include -void handler (int i) { std::_Exit(i); } -int main () -{ - struct sigaction act; - act.sa_handler = &handler; - sigemptyset (&act.sa_mask); - act.sa_flags = 0; - sigaction (1, &act, nullptr); - const int nsig = NSIG; - return 0; -} -''', cpp_flags, ld_flags) - report ('Yes\n') - cpp_flags += [ '-DMRTRIX_SIGNAL_HANDLING' ] - -except: - report ('No\n') - - - - diff --git a/lib/signal_handler.cpp b/lib/signal_handler.cpp index cbe3e510d4..ee6a8fcdc7 100644 --- a/lib/signal_handler.cpp +++ b/lib/signal_handler.cpp @@ -17,9 +17,7 @@ #include #include -#ifdef MRTRIX_SIGNAL_HANDLING #include -#endif #include "exception.h" #include "file/path.h" @@ -36,7 +34,53 @@ namespace MR SignalHandler::SignalHandler() { -#ifdef MRTRIX_SIGNAL_HANDLING +#ifdef MRTRIX_WINDOWS + // Use signal() rather than sigaction() for Windows, as the latter is not supported + // Set the handler for a priori known signals only +#ifdef SIGALRM + signal (SIGALRM, handler); +#endif +#ifdef SIGBUS + signal (SIGBUS, handler); +#endif +#ifdef SIGFPE + signal (SIGFPE, handler); +#endif +#ifdef SIGHUP + signal (SIGHUP, handler); +#endif +#ifdef SIGILL // Note: Not generated under Windows + signal (SIGILL, handler); +#endif +#ifdef SIGINT // Note: Not supported for any Win32 application + signal (SIGINT, handler); +#endif +#ifdef SIGPIPE + signal (SIGPIPE, handler); +#endif +#ifdef SIGPWR + signal (SIGPWR, handler); +#endif +#ifdef SIGQUIT + signal (SIGQUIT, handler); +#endif +#ifdef SIGSEGV + signal (SIGSEGV, handler); +#endif +#ifdef SIGSYS + signal (SIGSYS, handler); +#endif +#ifdef SIGTERM // Note: Not generated under Windows + signal (SIGTERM, handler); +#endif +#ifdef SIGXCPU + signal (SIGXCPU, handler); +#endif +#ifdef SIGXFSZ + signal (SIGXFSZ, handler); +#endif + +#else // Construct the signal structure struct sigaction act; @@ -120,6 +164,14 @@ namespace MR { // Only show one signal error message if multi-threading std::lock_guard lock (mutex); + + // Try to do a tempfile cleanup before printing the error, since the latter's not guaranteed to work... + for (auto i : data) { + if (Path::exists (i)) + // Don't use File::unlink: may throw an exception + ::unlink (i.c_str()); + } + // Don't attempt to use any terminal colouring switch (i) { #ifdef SIGALRM @@ -196,11 +248,6 @@ namespace MR std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: " << i << "] Unknown system signal\n"; } - for (auto i : data) { - if (Path::exists (i)) - // Don't use File::unlink: may throw an exception - ::unlink (i.c_str()); - } std::_Exit (i); } From 152bbfcf3c808a561c141adf6955156ee6805902 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 21 Sep 2016 12:48:11 +1000 Subject: [PATCH 146/723] Safer system signal handling Use basic C functions and an atomic_flag in signal handling to reduce chances of further corruption. --- lib/signal_handler.cpp | 111 ++++++++++++++++++++--------------------- lib/signal_handler.h | 4 +- 2 files changed, 57 insertions(+), 58 deletions(-) diff --git a/lib/signal_handler.cpp b/lib/signal_handler.cpp index ee6a8fcdc7..5800100b00 100644 --- a/lib/signal_handler.cpp +++ b/lib/signal_handler.cpp @@ -22,13 +22,17 @@ #include "exception.h" #include "file/path.h" +#ifdef MRTRIX_WINDOWS +#define STDERR_FILENO 2 +#endif + namespace MR { std::vector SignalHandler::data; - std::mutex SignalHandler::mutex; + std::atomic_flag SignalHandler::flag = ATOMIC_FLAG_INIT; @@ -141,13 +145,14 @@ namespace MR void SignalHandler::operator+= (const std::string& s) { - std::lock_guard lock (mutex); + while (!flag.test_and_set(std::memory_order_seq_cst)); data.push_back (s); + flag.clear (std::memory_order_seq_cst); } void SignalHandler::operator-= (const std::string& s) { - std::lock_guard lock (mutex); + while (!flag.test_and_set (std::memory_order_seq_cst)); auto i = data.begin(); while (i != data.end()) { if (*i == s) @@ -155,6 +160,7 @@ namespace MR else ++i; } + flag.clear (std::memory_order_seq_cst); } @@ -163,92 +169,85 @@ namespace MR void SignalHandler::handler (int i) noexcept { // Only show one signal error message if multi-threading - std::lock_guard lock (mutex); + if (atomic_flag_test_and_set_explicit (&flag, std::memory_order_seq_cst)) { - // Try to do a tempfile cleanup before printing the error, since the latter's not guaranteed to work... - for (auto i : data) { - if (Path::exists (i)) + // Try to do a tempfile cleanup before printing the error, since the latter's not guaranteed to work... + for (auto i : data) { // Don't use File::unlink: may throw an exception ::unlink (i.c_str()); - } + } + + // Don't use std::cerr << here: Use basic C string-handling functions and a write() call to STDERR_FILENO - // Don't attempt to use any terminal colouring - switch (i) { +#define PRINT_SIGNAL(sig,msg) \ + case sig: \ + strcat (s, #sig); \ + strcat (s, " ("); \ + snprintf (i_string, 2, "%d", sig); \ + strcat (s, i_string); \ + strcat (s, ")] "); \ + strcat (s, msg); \ + break; + + + char s[128] = "\n"; + strcat (s, App::NAME.c_str()); + strcat (s, ": [SYSTEM FATAL CODE: "); + char i_string[3]; // Max 2 digits, plus null-terminator + + // Don't attempt to use any terminal colouring + switch (i) { #ifdef SIGALRM - case SIGALRM: - std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGALRM (" << SIGALRM << ")] Timer expiration\n"; - break; + PRINT_SIGNAL(SIGALRM,"Timer expiration"); #endif #ifdef SIGBUS - case SIGBUS: - std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGBUS (" << SIGBUS << ")] Bus error: Accessing invalid address (out of storage space?)\n"; - break; + PRINT_SIGNAL(SIGBUS,"Bus error: Accessing invalid address (out of storage space?)"); #endif #ifdef SIGFPE - case SIGFPE: - std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGFPE (" << SIGFPE << ")] Floating-point arithmetic exception\n"; - break; + PRINT_SIGNAL(SIGFPE,"Floating-point arithmetic exception"); #endif #ifdef SIGHUP - case SIGHUP: - std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGHUP (" << SIGHUP << ")] Disconnection of terminal\n"; - break; + PRINT_SIGNAL(SIGHUP,"Disconnection of terminal"); #endif #ifdef SIGILL - case SIGILL: - std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGILL (" << SIGILL << ")] Illegal instruction (corrupt binary command file?)\n"; - break; + PRINT_SIGNAL(SIGILL,"Illegal instruction (corrupt binary command file?)"); #endif #ifdef SIGINT - case SIGINT: - std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGINT (" << SIGINT << ")] Program manually interrupted by terminal\n"; - break; + PRINT_SIGNAL(SIGINT,"Program manually interrupted by terminal"); #endif #ifdef SIGPIPE - case SIGPIPE: - std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGPIPE (" << SIGPIPE << ")] Nothing on receiving end of pipe\n"; - break; + PRINT_SIGNAL(SIGPIPE,"Nothing on receiving end of pipe"); #endif #ifdef SIGPWR - case SIGPWR: - std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGPWR (" << SIGPWR << ")] Power failure restart\n"; - break; + PRINT_SIGNAL(SIGPWR,"Power failure restart"); #endif #ifdef SIGQUIT - case SIGQUIT: - std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGQUIT (" << SIGQUIT << ")] Received terminal quit signal\n"; - break; + PRINT_SIGNAL(SIGQUIT,"Received terminal quit signal"); #endif #ifdef SIGSEGV - case SIGSEGV: - std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGSEGV (" << SIGSEGV << ")] Segmentation fault: Invalid memory reference\n"; - break; + PRINT_SIGNAL(SIGSEGV,"Segmentation fault: Invalid memory reference"); #endif #ifdef SIGSYS - case SIGSYS: - std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGSYS (" << SIGSYS << ")] Bad system call\n"; - break; + PRINT_SIGNAL(SIGSYS,"Bad system call"); #endif #ifdef SIGTERM - case SIGTERM: - std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGTERM (" << SIGTERM << ")] Terminated by kill command\n"; - break; + PRINT_SIGNAL(SIGTERM,"Terminated by kill command"); #endif #ifdef SIGXCPU - case SIGXCPU: - std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGXCPU (" << SIGXCPU << ")] CPU time limit exceeded\n"; - break; + PRINT_SIGNAL(SIGXCPU,"CPU time limit exceeded"); #endif #ifdef SIGXFSZ - case SIGXFSZ: - std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: SIGXFSZ (" << SIGXFSZ << ")] File size limit exceeded\n"; - break; + PRINT_SIGNAL(SIGXFSZ,"File size limit exceeded"); #endif - default: - std::cerr << "\n" << App::NAME << ": [SYSTEM FATAL CODE: " << i << "] Unknown system signal\n"; - } + default: + strcat (s, "?] Unknown fatal system signal"); + break; + } + strcat (s, "\n"); + write (STDERR_FILENO, s, sizeof(s) - 1); - std::_Exit (i); + std::_Exit (i); + } } diff --git a/lib/signal_handler.h b/lib/signal_handler.h index 1dd24e3078..34f581614b 100644 --- a/lib/signal_handler.h +++ b/lib/signal_handler.h @@ -16,7 +16,7 @@ #ifndef __signal_handler_h__ #define __signal_handler_h__ -#include +#include #include #include @@ -36,7 +36,7 @@ namespace MR private: static std::vector data; - static std::mutex mutex; + static std::atomic_flag flag; static void on_exit() noexcept; static void handler (int) noexcept; From f6b87a2dbbfd8c3edca78d559cd4fc3aef0fbc2f Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 22 Sep 2016 13:10:52 +1000 Subject: [PATCH 147/723] Add AAL2 connectome LUT file --- src/connectome/tables/aal.txt | 2 +- src/connectome/tables/aal2.txt | 124 +++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 src/connectome/tables/aal2.txt diff --git a/src/connectome/tables/aal.txt b/src/connectome/tables/aal.txt index ad9ea72d2b..30fedb6473 100644 --- a/src/connectome/tables/aal.txt +++ b/src/connectome/tables/aal.txt @@ -1,5 +1,5 @@ -0 ??? Unknown 0 0 0 0 +0 ??? Unknown 0 0 0 0 1 PREL Precentral_L 243 231 117 255 2 F1L Frontal_Sup_L 212 91 65 255 diff --git a/src/connectome/tables/aal2.txt b/src/connectome/tables/aal2.txt new file mode 100644 index 0000000000..b999da5db8 --- /dev/null +++ b/src/connectome/tables/aal2.txt @@ -0,0 +1,124 @@ + +0 ??? Unknown 0 0 0 0 + +1 FAL Precentral_L 243 231 117 255 +2 FAR Precentral_R 243 231 117 255 +3 F1_2L Frontal_Sup_2_L 212 91 65 255 +4 F1_2R Frontal_Sup_2_R 212 91 65 255 +5 F2_2L Frontal_Mid_2_L 157 179 83 255 +6 F2_2R Frontal_Mid_2_R 157 179 83 255 +7 F3OPL Frontal_Inf_Oper_L 192 207 213 255 +8 F3OPR Frontal_Inf_Oper_R 192 207 213 255 +9 F3TL Frontal_Inf_Tri_L 220 245 129 255 +10 F3TR Frontal_Inf_Tri_R 220 245 129 255 +11 F3O_2L Frontal_Inf_Orb_2_L 183 145 163 255 +12 F3O_2R Frontal_Inf_Orb_2_R 183 145 163 255 +13 ORL Rolandic_Oper_L 65 39 86 255 +14 ORR Rolandic_Oper_R 65 39 86 255 +15 SMAL Supp_Motor_Area_L 56 34 80 255 +16 SMAR Supp_Motor_Area_R 56 34 80 255 +17 COBL Olfactory_L 136 45 97 255 +18 COBR Olfactory_R 136 45 97 255 +19 FML Frontal_Sup_Medial_L 177 194 148 255 +20 FMR Frontal_Sup_Medial_R 177 194 148 255 +21 FMOL Frontal_Med_Orb_L 226 142 69 255 +22 FMOR Frontal_Med_Orb_R 226 142 69 255 +23 GRL Rectus_L 159 179 141 255 +24 GRR Rectus_R 159 179 141 255 +25 OFCMEDL OFCmed_L 228 63 161 255 +26 OFCMEDR OFCmed_R 228 63 161 255 +27 OFCANTL OFCant_L 252 255 164 255 +28 OFCANTR OFCant_R 252 255 164 255 +29 OFCPOSTL OFCpost_L 225 240 245 255 +30 OFCPOSTR OFCpost_R 225 240 245 255 +31 OFCLATL OFClat_L 190 253 164 255 +32 OFCLATR OFClat_R 190 253 164 255 +33 INL Insula_L 111 120 63 255 +34 INR Insula_R 111 120 63 255 +35 CIAL Cingulate_Ant_L 164 90 126 255 +36 CIAR Cingulate_Ant_R 164 90 126 255 +37 CINML Cingulate_Mid_L 131 156 62 255 +38 CINMR Cingulate_Mid_R 131 156 62 255 +39 CIPL Cingulate_Post_L 184 161 54 255 +40 CIPR Cingulate_Post_R 184 161 54 255 +41 HIPPOL Hippocampus_L 55 34 83 255 +42 HIPPOR Hippocampus_R 55 34 83 255 +43 PARA_HIPPOL ParaHippocampal_L 140 66 68 255 +44 PARA_HIPPOR ParaHippocampal_R 140 66 68 255 +45 AMYGDL Amygdala_L 246 224 90 255 +46 AMYGDR Amygdala_R 246 224 90 255 +47 V1L Calcarine_L 255 255 255 255 +48 V1R Calcarine_R 255 255 255 255 +49 QL Cuneus_L 111 115 68 255 +50 QR Cuneus_R 111 115 68 255 +51 LINGL Lingual_L 155 65 63 255 +52 LINGR Lingual_R 155 65 63 255 +53 O1L Occipital_Sup_L 200 117 72 255 +54 O1R Occipital_Sup_R 200 117 72 255 +55 O2L Occipital_Mid_L 186 70 94 255 +56 O2R Occipital_Mid_R 186 70 94 255 +57 O3L Occipital_Inf_L 163 182 79 255 +58 O3R Occipital_Inf_R 163 182 79 255 +59 FUSIL Fusiform_L 203 79 107 255 +60 FUSIR Fusiform_R 203 79 107 255 +61 PAL Postcentral_L 97 110 64 255 +62 PAR Postcentral_R 97 110 64 255 +63 P1L Parietal_Sup_L 138 168 107 255 +64 P1R Parietal_Sup_R 138 168 107 255 +65 P2L Parietal_Inf_L 77 78 117 255 +66 P2R Parietal_Inf_R 77 78 117 255 +67 GSML SupraMarginal_L 85 45 94 255 +68 GSMR SupraMarginal_R 85 45 94 255 +69 GAL Angular_L 109 93 128 255 +70 GAR Angular_R 109 93 128 255 +71 PQL Precuneus_L 137 54 72 255 +72 PQR Precuneus_R 137 54 72 255 +73 LPCL Paracentral_Lobule_L 151 126 57 255 +74 LPCR Paracentral_Lobule_R 151 126 57 255 +75 NCL Caudate_L 133 32 87 255 +76 NCR Caudate_R 133 32 87 255 +77 NLL Putamen_L 88 113 113 255 +78 NLR Putamen_R 88 113 113 255 +79 PALLL Pallidum_L 46 37 77 255 +80 PALLR Pallidum_R 46 37 77 255 +81 THAL Thalamus_L 155 177 115 255 +82 THAR Thalamus_R 155 177 115 255 +83 HESCHLL Heschl_L 172 189 88 255 +84 HESCHLR Heschl_R 172 189 88 255 +85 T1L Temporal_Sup_L 224 117 61 255 +86 T1R Temporal_Sup_R 224 117 61 255 +87 T1AL Temporal_Pole_Sup_L 203 84 90 255 +88 T1AR Temporal_Pole_Sup_R 203 84 90 255 +89 T2L Temporal_Mid_L 54 45 97 255 +90 T2R Temporal_Mid_R 54 45 97 255 +91 T2AL Temporal_Pole_Mid_L 161 183 97 255 +92 T2AR Temporal_Pole_Mid_R 161 183 97 255 +93 T3L Temporal_Inf_L 132 135 164 255 +94 T3R Temporal_Inf_R 132 135 164 255 +95 CERCRU1L Cerebelum_Crus1_L 116 16 212 255 +96 CERCRU1R Cerebelum_Crus1_R 116 16 212 255 +97 CERCRU2L Cerebelum_Crus2_L 30 236 29 255 +98 CERCRU2R Cerebelum_Crus2_R 30 236 29 255 +99 CER3L Cerebelum_3_L 35 36 242 255 +100 CER3R Cerebelum_3_R 35 36 242 255 +101 CER4_5L Cerebelum_4_5_L 36 240 239 255 +102 CER4_5R Cerebelum_4_5_R 36 240 239 255 +103 CER6L Cerebelum_6_L 238 30 238 255 +104 CER6R Cerebelum_6_R 238 30 238 255 +105 CER7BL Cerebelum_7b_L 150 67 27 255 +106 CER7BR Cerebelum_7b_R 150 67 27 255 +107 CER8L Cerebelum_8_L 237 236 30 255 +108 CER8R Cerebelum_8_R 237 236 30 255 +109 CER9L Cerebelum_9_L 35 157 36 255 +110 CER9R Cerebelum_9_R 35 157 36 255 +111 CER10L Cerebelum_10_L 226 22 23 255 +112 CER10R Cerebelum_10_R 226 22 23 255 +113 VER1_2 Vermis_1_2 73 126 151 128 +114 VER3 Vermis_3 18 18 121 128 +115 VER4_5 Vermis_4_5 18 120 120 128 +116 VER6 Vermis_6 119 15 119 128 +117 VER7 Vermis_7 75 33 33 128 +118 VER8 Vermis_8 118 118 15 128 +119 VER9 Vermis_9 18 78 18 128 +120 VER10 Vermis_10 113 11 11 128 + From 57e846d8cf92dfb826d74dd0b33e30afacf10158 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 22 Sep 2016 13:21:24 +1000 Subject: [PATCH 148/723] Few tweeks to fixel helper. Index no longer requires n_fixel_key. Also can handly .gz files --- cmd/fixelcfestats.cpp | 6 ++- docs/reference/commands_list.rst | 2 - lib/file/path.h | 4 +- lib/fixel_format/helpers.h | 93 ++++++++++++++++++++++++-------- lib/fixel_format/keys.h | 2 +- 5 files changed, 79 insertions(+), 28 deletions(-) diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 1a5a58406f..ab5663936b 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -150,10 +150,12 @@ void run() { const std::string input_fixel_folder = argument[0]; Header index_header = FixelFormat::find_index_header (input_fixel_folder); + std::cout << index_header.valid() << std::endl; + std::cout << index_header << std::endl; auto index_image = index_header.get_image(); + TRACE; - - uint32_t num_fixels = std::stoul (index_image.keyval().at(FixelFormat::n_fixels_key)); + uint32_t num_fixels = FixelFormat::get_number_of_fixels (index_header); CONSOLE ("number of fixels: " + str(num_fixels)); std::vector positions (num_fixels); diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index 6409d79448..00bfc2aaed 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -17,8 +17,6 @@ List of MRtrix3 commands commands/5ttedit - commands/addnoise - commands/afdconnectivity commands/amp2sh diff --git a/lib/file/path.h b/lib/file/path.h index 83f39d7b4a..1eaf5a5aa3 100644 --- a/lib/file/path.h +++ b/lib/file/path.h @@ -105,11 +105,11 @@ namespace MR name.substr (name.size()-suffix.size()) == suffix); } - inline bool has_suffix(const std::string&name, const std::initializer_list &suffix_list) + inline bool has_suffix (const std::string&name, const std::initializer_list &suffix_list) { bool flag(false); - for(const auto& suffix : suffix_list) { flag = flag || has_suffix(name, suffix); } + for(const auto& suffix : suffix_list) { flag = flag || has_suffix (name, suffix); } return flag; } diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index 1ed1534acb..7547101c44 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -18,6 +18,7 @@ #include "formats/mrtrix_utils.h" #include "fixel_format/keys.h" +#include "algo/loop.h" namespace MR { @@ -33,14 +34,24 @@ namespace MR { inline bool is_index_image (const Header& in) { - return (in.size(2) != 1) && in.keyval().count (n_fixels_key); + bool is_index = false; + if (in.ndim() == 4) { + if (in.size(3) == 2) { + for (std::initializer_list::iterator it = FixelFormat::supported_fixel_formats.begin(); + it != FixelFormat::supported_fixel_formats.end(); ++it) { + if (Path::basename (in.name()) == "index" + *it) + is_index = true; + } + } + } + return is_index; } - - inline void check_index_image (const Header& in) + template + inline void check_index_image (const IndexHeaderType& index) { - if (!is_index_image(in)) - throw InvalidImageException (in.name () + " is not a valid fixel index image. Header key " + n_fixels_key + " not found"); + if (!is_index_image (index)) + throw InvalidImageException (index.name() + " is not a valid fixel index image. Image must be 4D with 2 volumes in the 4th dimension"); } @@ -71,13 +82,55 @@ namespace MR return fixel_folder; } + + template + inline uint32_t get_number_of_fixels (IndexHeaderType& index_header) { + check_index_image (index_header); + if (index_header.keyval().count (n_fixels_key)) { + return std::stoul (index_header.keyval().at(n_fixels_key)); + } else { + auto index_image = Image::open (index_header.name()); + index_image.index(3) = 1; + uint32_t num_fixels = 0; + uint32_t max_offset = 0; + for (auto i = MR::Loop (index_image, 0, 3) (index_image); i; ++i) { + if (index_image.value() > max_offset) { + max_offset = index_image.value(); + index_image.index(3) = 0; + num_fixels = index_image.value(); + index_image.index(3) = 1; + } + } + return (max_offset + num_fixels); + } + } + + + template inline bool fixels_match (const IndexHeaderType& index_header, const DataHeaderType& data_header) { bool fixels_match (false); - if (is_index_image (index_header)) - fixels_match = std::stoul(index_header.keyval ().at (n_fixels_key)) == (unsigned long)data_header.size (0); + if (is_index_image (index_header)) { + if (index_header.keyval().count (n_fixels_key)) { + fixels_match = std::stoul (index_header.keyval().at(n_fixels_key)) == (uint32_t)data_header.size(0); + } else { + auto index_image = Image::open (index_header.name()); + index_image.index(3) = 1; + uint32_t num_fixels = 0; + uint32_t max_offset = 0; + for (auto i = MR::Loop (index_image, 0, 3) (index_image); i; ++i) { + if (index_image.value() > max_offset) { + max_offset = index_image.value(); + index_image.index(3) = 0; + num_fixels = index_image.value(); + index_image.index(3) = 1; + } + } + fixels_match = (max_offset + num_fixels) == (uint32_t)data_header.size(0); + } + } return fixels_match; } @@ -116,31 +169,29 @@ namespace MR inline Header find_index_header (const std::string &fixel_folder_path) { - bool index_found (false); - Header H; + Header header; check_fixel_folder (fixel_folder_path); - auto dir_walker = Path::Dir (fixel_folder_path); - std::string fname; - while ((fname = dir_walker.read_name ()).size ()) { - auto full_path = Path::join (fixel_folder_path, fname); - if (Path::has_suffix (fname, FixelFormat::supported_fixel_formats) && is_index_image (H = Header::open (full_path))) { - index_found = true; - break; + for (std::initializer_list::iterator it = FixelFormat::supported_fixel_formats.begin(); + it != FixelFormat::supported_fixel_formats.end(); ++it) { + std::string full_path = Path::join (fixel_folder_path, "index" + *it); + if (Path::exists(full_path)) { + if (header.valid()) + throw InvalidFixelDirectoryException ("Multiple index images found in directory " + fixel_folder_path); + header = Header::open (full_path); } } - - if (!index_found) + if (!header.valid()) throw InvalidFixelDirectoryException ("Could not find index image in directory " + fixel_folder_path); - return H; + check_index_image (header); + return header; } inline std::vector
find_data_headers (const std::string &fixel_folder_path, const Header &index_header, const bool include_directions = false) { check_index_image (index_header); - std::vector
data_headers; auto dir_walker = Path::Dir (fixel_folder_path); @@ -153,7 +204,7 @@ namespace MR if (!is_directions_file (H) || include_directions) data_headers.emplace_back (std::move (H)); } else { - WARN ("fixel data file (" + fname + ") does not contain the same number of elements as fixels in the index file" ); + WARN ("fixel data file (" + fname + ") does not contain the same number of elements as fixels in the index file"); } } } diff --git a/lib/fixel_format/keys.h b/lib/fixel_format/keys.h index 643ba898e8..9f992a9007 100644 --- a/lib/fixel_format/keys.h +++ b/lib/fixel_format/keys.h @@ -23,7 +23,7 @@ namespace MR namespace FixelFormat { const std::string n_fixels_key ("nfixels"); - const std::initializer_list supported_fixel_formats { ".mif", ".nii" }; + const std::initializer_list supported_fixel_formats { ".mif", ".nii", ".mif.gz" , ".nii.gz" }; } } From 4379322225560f583a8e3ce6a971a9d80a8f8d29 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 22 Sep 2016 17:07:53 +1000 Subject: [PATCH 149/723] Ported testing_diff_fixel to new format --- testing/cmd/testing_diff_data.cpp | 116 +++++----------------------- testing/cmd/testing_diff_dir.cpp | 33 ++++---- testing/cmd/testing_diff_fixel.cpp | 115 +++++++++++---------------- testing/cmd/testing_diff_matrix.cpp | 33 ++++---- testing/cmd/testing_diff_mesh.cpp | 32 ++++---- testing/cmd/testing_diff_peaks.cpp | 33 ++++---- testing/cmd/testing_diff_tck.cpp | 33 ++++---- testing/cmd/testing_diff_tsf.cpp | 33 ++++---- testing/cmd/testing_gen_data.cpp | 33 ++++---- 9 files changed, 153 insertions(+), 308 deletions(-) diff --git a/testing/cmd/testing_diff_data.cpp b/testing/cmd/testing_diff_data.cpp index 4769fbdc07..7c51d52ba9 100644 --- a/testing/cmd/testing_diff_data.cpp +++ b/testing/cmd/testing_diff_data.cpp @@ -1,33 +1,25 @@ - /* - Copyright 2008 Brain Research Institute, Melbourne, Australia - - Written by J-Donald Tournier, 27/06/08. - - This file is part of MRtrix. - - MRtrix is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - MRtrix is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ - You should have received a copy of the GNU General Public License - along with MRtrix. If not, see . - -*/ #include "command.h" -#include "progressbar.h" #include "datatype.h" +#include "progressbar.h" #include "image.h" -#include "algo/loop.h" -#include "algo/threaded_loop.h" +#include "diff_images.h" using namespace MR; using namespace App; @@ -44,12 +36,8 @@ void usage () + Argument ("data2", "another image.").type_image_in(); OPTIONS - + Option ("abs", "specify an absolute tolerance") - + Argument ("tolerance").type_float (0.0) - + Option ("frac", "specify a fractional tolerance") - + Argument ("tolerance").type_float (0.0) - + Option ("voxel", "specify a fractional tolerance relative to the maximum value in the voxel") - + Argument ("tolerance").type_float (0.0); + + Testing::Diff_Image_Options; + } @@ -57,76 +45,8 @@ void run () { auto in1 = Image::open (argument[0]); auto in2 = Image::open (argument[1]); - check_dimensions (in1, in2); - for (size_t i = 0; i < in1.ndim(); ++i) { - if (std::isfinite (in1.size(i))) - if (in1.size(i) != in2.size(i)) - throw Exception ("images \"" + in1.name() + "\" and \"" + in2.name() + "\" do not have matching voxel spacings " + - str(in1.size(i)) + " vs " + str(in2.size(i))); - } - for (size_t i = 0; i < 3; ++i) { - for (size_t j = 0; j < 4; ++j) { - if (std::abs (in1.transform().matrix()(i,j) - in2.transform().matrix()(i,j)) > 0.001) - throw Exception ("images \"" + in1.name() + "\" and \"" + in2.name() + "\" do not have matching header transforms " - + "\n" + str(in1.transform().matrix()) + "vs \n " + str(in2.transform().matrix()) + ")"); - } - } - - auto opt = get_options ("frac"); - if (opt.size()) { - - const double tol = opt[0][0]; - - ThreadedLoop (in1) - .run ([&tol] (const decltype(in1)& a, const decltype(in2)& b) { - if (std::abs ((a.value() - b.value()) / (0.5 * (a.value() + b.value()))) > tol) - throw Exception ("images \"" + a.name() + "\" and \"" + b.name() + "\" do not match within fractional precision of " + str(tol) - + " (" + str(cdouble (a.value())) + " vs " + str(cdouble (b.value())) + ")"); - }, in1, in2); - - } else { - - opt = get_options ("voxel"); - if (opt.size()) { - - if (in1.ndim() != 4) - throw Exception ("Option -voxel only works for 4D images"); - - const double tol = opt[0][0]; - - auto func = [&tol] (decltype(in1)& a, decltype(in2)& b) { - double maxa = 0.0, maxb = 0.0; - for (auto l = Loop(3) (a, b); l; ++l) { - maxa = std::max (maxa, std::abs (cdouble(a.value()))); - maxb = std::max (maxb, std::abs (cdouble(b.value()))); - } - const double threshold = tol * 0.5 * (maxa + maxb); - for (auto l = Loop(3) (a, b); l; ++l) { - if (std::abs (cdouble (a.value()) - cdouble (b.value())) > threshold) - throw Exception ("images \"" + a.name() + "\" and \"" + b.name() + "\" do not match within " + str(tol) + " of maximal voxel value" - + " (" + str(cdouble (a.value())) + " vs " + str(cdouble (b.value())) + ")"); - } - }; - - ThreadedLoop (in1, 0, 3).run (func, in1, in2); - - } else { - - double tol = 0.0; - opt = get_options ("abs"); - if (opt.size()) - tol = opt[0][0]; - - ThreadedLoop (in1) - .run ([&tol] (const decltype(in1)& a, const decltype(in2)& b) { - if (std::abs (a.value() - b.value()) > tol) - throw Exception ("images \"" + a.name() + "\" and \"" + b.name() + "\" do not match within absolute precision of " + str(tol) - + " (" + str(cdouble (a.value())) + " vs " + str(cdouble (b.value())) + ")"); - }, in1, in2); - } - - } + Testing::diff_images (in1, in2); CONSOLE ("data checked OK"); } diff --git a/testing/cmd/testing_diff_dir.cpp b/testing/cmd/testing_diff_dir.cpp index 57ab3d2df6..9e4a01f95f 100644 --- a/testing/cmd/testing_diff_dir.cpp +++ b/testing/cmd/testing_diff_dir.cpp @@ -1,25 +1,18 @@ - /* - Copyright 2008 Brain Research Institute, Melbourne, Australia - - Written by David Raffelt - - This file is part of MRtrix. - - MRtrix is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - MRtrix is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with MRtrix. If not, see . + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ -*/ #include "command.h" #include "progressbar.h" diff --git a/testing/cmd/testing_diff_fixel.cpp b/testing/cmd/testing_diff_fixel.cpp index 998fb0afe5..2887797ac0 100644 --- a/testing/cmd/testing_diff_fixel.cpp +++ b/testing/cmd/testing_diff_fixel.cpp @@ -1,37 +1,26 @@ - /* - Copyright 2008 Brain Research Institute, Melbourne, Australia - - Written by J-Donald Tournier, 27/06/08. - - This file is part of MRtrix. - - MRtrix is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - MRtrix is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with MRtrix. If not, see . - -*/ + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ #include "command.h" -#include "progressbar.h" #include "datatype.h" #include "image.h" -#include "sparse/image.h" -#include "sparse/fixel_metric.h" -#include "image_helpers.h" -#include "algo/threaded_loop.h" -using MR::Sparse::FixelMetric; +#include "fixel_format/helpers.h" + +#include "diff_images.h" using namespace MR; using namespace App; @@ -44,57 +33,41 @@ void usage () + "compare two fixel images for differences, within specified tolerance."; ARGUMENTS - + Argument ("data1", "a fixel image.").type_image_in () - + Argument ("data2", "another fixel image.").type_image_in () - + Argument ("tolerance", "the amount of signal difference to consider acceptable").type_float (0.0); + + Argument ("fixel1", "fixel folder.").type_text () + + Argument ("fixel2", "another fixel folder.").type_text (); + + OPTIONS + + Testing::Diff_Image_Options; + } void run () { - Sparse::Image buffer1 (argument[0]); - Sparse::Image buffer2 (argument[1]); - check_dimensions (buffer1, buffer2); - for (size_t i = 0; i < buffer1.ndim(); ++i) { - if (std::isfinite (buffer1.spacing(i))) - if (buffer1.spacing(i) != buffer2.spacing(i)) - throw Exception ("images \"" + buffer1.name() + "\" and \"" + buffer2.name() + "\" do not have matching voxel spacings " + - str(buffer1.spacing(i)) + " vs " + str(buffer2.spacing(i))); + std::string fixel_folder1 = argument[0]; + FixelFormat::check_fixel_folder (fixel_folder1); + std::string fixel_folder2 = argument[1]; + FixelFormat::check_fixel_folder (fixel_folder2); + + if (fixel_folder1 == fixel_folder2) + throw Exception ("Input fixel folders are the same"); + + auto dir_walker1 = Path::Dir (fixel_folder1); + std::string fname; + while ((fname = dir_walker1.read_name()).size()) { + auto in1 = Image::open (Path::join (fixel_folder1, fname)); + std::string filename2 = Path::join (fixel_folder2, fname); + if (!Path::exists (filename2)) + throw Exception ("File (" + fname + ") exists in fixel folder (" + fixel_folder1 + ") but not in fixel folder (" + fixel_folder2 + ") "); + auto in2 = Image::open (filename2); + Testing::diff_images (in1, in2); } - for (size_t i = 0; i < 3; ++i) { - for (size_t j = 0; j < 4; ++j) { - if (std::abs (buffer1.transform()(i,j) - buffer2.transform()(i,j)) > 0.001) - throw Exception ("images \"" + buffer1.name() + "\" and \"" + buffer2.name() + "\" do not have matching header transforms " - + "\n" + str(buffer1.transform().matrix()) + "vs \n " + str(buffer2.transform().matrix()) + ")"); - } + auto dir_walker2 = Path::Dir (fixel_folder2); + while ((fname = dir_walker2.read_name()).size()) { + std::string filename1 = Path::join (fixel_folder1, fname); + if (!Path::exists (filename1)) + throw Exception ("File (" + fname + ") exists in fixel folder (" + fixel_folder2 + ") but not in fixel folder (" + fixel_folder1 + ") "); } - - double tol = argument[2]; - - ThreadedLoop (buffer1) - .run ([&tol] (decltype(buffer1)& a, decltype(buffer2)& b) { - if (a.value().size() != b.value().size()) - throw Exception ("the fixel images do not have corresponding fixels in all voxels"); - // For each fixel - for (size_t fixel = 0; fixel != a.value().size(); ++fixel) { - // Check value - if (std::abs (a.value()[fixel].value - b.value()[fixel].value) > tol) - throw Exception ("images \"" + a.name() + "\" and \"" + b.name() + "\" do not match fixel value within specified precision of " + str(tol) - + " (" + str(a.value()[fixel].value) + " vs " + str(b.value()[fixel].value) + ")"); - // Check size - if (std::abs (a.value()[fixel].size - b.value()[fixel].size) > tol) - throw Exception ("images \"" + a.name() + "\" and \"" + b.name() + "\" do not match fixel size within specified precision of " + str(tol) - + " (" + str(a.value()[fixel].size) + " vs " + str(b.value()[fixel].size) + ")"); - // Check Direction - for (size_t dim = 0; dim < 3; ++dim) { - if (std::abs (a.value()[fixel].dir[dim] - b.value()[fixel].dir[dim]) > tol) - throw Exception ("images \"" + a.name() + "\" and \"" + b.name() + "\" do not match fixel direction within specified precision of " + str(tol) - + " (" + str(a.value()[fixel].dir[dim]) + " vs " + str(b.value()[fixel].dir[dim]) + ")"); - } - } - }, buffer1, buffer2); - - CONSOLE ("data checked OK"); } diff --git a/testing/cmd/testing_diff_matrix.cpp b/testing/cmd/testing_diff_matrix.cpp index f6857c14dd..93600c15eb 100644 --- a/testing/cmd/testing_diff_matrix.cpp +++ b/testing/cmd/testing_diff_matrix.cpp @@ -1,25 +1,18 @@ - /* - Copyright 2008 Brain Research Institute, Melbourne, Australia - - Written by Robert E. Smith, 2015. - - This file is part of MRtrix. - - MRtrix is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - MRtrix is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with MRtrix. If not, see . + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ -*/ #include diff --git a/testing/cmd/testing_diff_mesh.cpp b/testing/cmd/testing_diff_mesh.cpp index e9a08b4bcd..0c07b389ca 100644 --- a/testing/cmd/testing_diff_mesh.cpp +++ b/testing/cmd/testing_diff_mesh.cpp @@ -1,24 +1,18 @@ /* - Copyright 2008 Brain Research Institute, Melbourne, Australia + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ - Written by Robert E. Smith, 2015. - - This file is part of MRtrix. - - MRtrix is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - MRtrix is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with MRtrix. If not, see . - -*/ #include "command.h" #include "progressbar.h" diff --git a/testing/cmd/testing_diff_peaks.cpp b/testing/cmd/testing_diff_peaks.cpp index bd19c91bcc..195d383455 100644 --- a/testing/cmd/testing_diff_peaks.cpp +++ b/testing/cmd/testing_diff_peaks.cpp @@ -1,25 +1,18 @@ - /* - Copyright 2008 Brain Research Institute, Melbourne, Australia - - Written by J-Donald Tournier, 27/06/08. - - This file is part of MRtrix. - - MRtrix is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - MRtrix is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with MRtrix. If not, see . + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ -*/ #include "command.h" #include "progressbar.h" diff --git a/testing/cmd/testing_diff_tck.cpp b/testing/cmd/testing_diff_tck.cpp index 609ef1d6da..4bf7edd386 100644 --- a/testing/cmd/testing_diff_tck.cpp +++ b/testing/cmd/testing_diff_tck.cpp @@ -1,24 +1,17 @@ /* - Copyright 2008 Brain Research Institute, Melbourne, Australia - - Written by J-Donald Tournier, 27/06/08. - - This file is part of MRtrix. - - MRtrix is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - MRtrix is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with MRtrix. If not, see . - -*/ + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ #include "command.h" #include "progressbar.h" diff --git a/testing/cmd/testing_diff_tsf.cpp b/testing/cmd/testing_diff_tsf.cpp index 8c6b5d9784..59b5146de7 100644 --- a/testing/cmd/testing_diff_tsf.cpp +++ b/testing/cmd/testing_diff_tsf.cpp @@ -1,24 +1,17 @@ /* - Copyright 2008 Brain Research Institute, Melbourne, Australia - - Written by David Raffelt - - This file is part of MRtrix. - - MRtrix is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - MRtrix is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with MRtrix. If not, see . - -*/ + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ #include "command.h" #include "progressbar.h" diff --git a/testing/cmd/testing_gen_data.cpp b/testing/cmd/testing_gen_data.cpp index e60278a42e..06176d824d 100644 --- a/testing/cmd/testing_gen_data.cpp +++ b/testing/cmd/testing_gen_data.cpp @@ -1,24 +1,17 @@ /* - Copyright 2008 Brain Research Institute, Melbourne, Australia - - Written by J-Donald Tournier, 27/06/08. - - This file is part of MRtrix. - - MRtrix is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - MRtrix is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with MRtrix. If not, see . - -*/ + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ #include "command.h" #include "progressbar.h" From b4b91c69ba222ce4392595bcbe8bbf0acfcebd15 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 22 Sep 2016 17:13:44 +1000 Subject: [PATCH 150/723] Renamed testing_diff_data to testing_diff_image --- testing/README.md | 2 +- ...g_diff_data.cpp => testing_diff_image.cpp} | 0 testing/tests/5tt2gmwmi | 2 +- testing/tests/5tt2vis | 2 +- testing/tests/5ttedit | 2 +- testing/tests/amp2sh | 2 +- testing/tests/dwi2adc | 2 +- testing/tests/dwi2fod | 10 +++---- testing/tests/dwi2mask | 2 +- testing/tests/dwi2noise | 2 +- testing/tests/dwi2tensor | 16 +++++----- testing/tests/dwidenoise | 12 ++++---- testing/tests/dwiextract | 4 +-- testing/tests/fixel2sh | 2 +- testing/tests/label2colour | 4 +-- testing/tests/labelconvert | 4 +-- testing/tests/maskfilter | 18 +++++------ testing/tests/mesh2pve | 2 +- testing/tests/mrcalc | 8 ++--- testing/tests/mrcat | 10 +++---- testing/tests/mrconvert | 22 +++++++------- testing/tests/mrcrop | 4 +-- testing/tests/mrfilter | 30 +++++++++---------- testing/tests/mrmath | 8 ++--- testing/tests/mrpad | 8 ++--- testing/tests/mrresize | 16 +++++----- testing/tests/mrthreshold | 20 ++++++------- testing/tests/mrtransform | 14 ++++----- testing/tests/peaks2amp | 2 +- testing/tests/sh2amp | 6 ++-- testing/tests/sh2power | 2 +- testing/tests/shbasis | 8 ++--- testing/tests/shconv | 4 +-- testing/tests/tckgen | 12 ++++---- testing/tests/tckmap | 8 ++--- testing/tests/tensor2metric | 16 +++++----- testing/tests/warpcorrect | 2 +- testing/tests/warpinit | 2 +- 38 files changed, 145 insertions(+), 145 deletions(-) rename testing/cmd/{testing_diff_data.cpp => testing_diff_image.cpp} (100%) diff --git a/testing/README.md b/testing/README.md index e4a8b2a510..695b5bcec3 100644 --- a/testing/README.md +++ b/testing/README.md @@ -27,7 +27,7 @@ Add a script to the `tests/` folder. Each line of these scripts constitutes a single test, and will be run as a single unit. Use && and || bash constructs if needed to create compound commands. Each of these lines should return a zero exit code if successful. You can test the output of your commands against your -expected output using the `testing_diff_data` command (note other commands are +expected output using the `testing_diff_image` command (note other commands are available to check various types of output). Note that this script will be invoked directly in the context set up by the diff --git a/testing/cmd/testing_diff_data.cpp b/testing/cmd/testing_diff_image.cpp similarity index 100% rename from testing/cmd/testing_diff_data.cpp rename to testing/cmd/testing_diff_image.cpp diff --git a/testing/tests/5tt2gmwmi b/testing/tests/5tt2gmwmi index 99404b5ce7..0517bf69c7 100644 --- a/testing/tests/5tt2gmwmi +++ b/testing/tests/5tt2gmwmi @@ -1 +1 @@ -5tt2gmwmi SIFT_phantom/5tt.mif - | testing_diff_data - 5tt2gmwmi/out.mif -abs 1e-3 +5tt2gmwmi SIFT_phantom/5tt.mif - | testing_diff_image - 5tt2gmwmi/out.mif -abs 1e-3 diff --git a/testing/tests/5tt2vis b/testing/tests/5tt2vis index 325cea3a2d..e1eec2ef2b 100644 --- a/testing/tests/5tt2vis +++ b/testing/tests/5tt2vis @@ -1 +1 @@ -5tt2vis SIFT_phantom/5tt.mif - | testing_diff_data - 5tt2vis/out.mif -abs 1e-3 +5tt2vis SIFT_phantom/5tt.mif - | testing_diff_image - 5tt2vis/out.mif -abs 1e-3 diff --git a/testing/tests/5ttedit b/testing/tests/5ttedit index c74e0eadda..1cbf2b5284 100644 --- a/testing/tests/5ttedit +++ b/testing/tests/5ttedit @@ -1,2 +1,2 @@ -5ttedit SIFT_phantom/5tt.mif - -cgm 5ttedit/in0.mif -sgm 5ttedit/in1.mif -wm 5ttedit/in2.mif -csf 5ttedit/in3.mif -path 5ttedit/in4.mif -none 5ttedit/none.mif | testing_diff_data - 5ttedit/out.mif +5ttedit SIFT_phantom/5tt.mif - -cgm 5ttedit/in0.mif -sgm 5ttedit/in1.mif -wm 5ttedit/in2.mif -csf 5ttedit/in3.mif -path 5ttedit/in4.mif -none 5ttedit/none.mif | testing_diff_image - 5ttedit/out.mif diff --git a/testing/tests/amp2sh b/testing/tests/amp2sh index a84cdbde92..a6341be4f9 100644 --- a/testing/tests/amp2sh +++ b/testing/tests/amp2sh @@ -1 +1 @@ -amp2sh dwi.mif - | testing_diff_data - amp2sh/out.mif -voxel 1e-5 \ No newline at end of file +amp2sh dwi.mif - | testing_diff_image - amp2sh/out.mif -voxel 1e-5 \ No newline at end of file diff --git a/testing/tests/dwi2adc b/testing/tests/dwi2adc index e0d10b758d..f61449953f 100644 --- a/testing/tests/dwi2adc +++ b/testing/tests/dwi2adc @@ -1 +1 @@ -dwi2adc dwi2adc/in.mif - | testing_diff_data - dwi2adc/out.mif -frac 1e-5 +dwi2adc dwi2adc/in.mif - | testing_diff_image - dwi2adc/out.mif -frac 1e-5 diff --git a/testing/tests/dwi2fod b/testing/tests/dwi2fod index 97ec3b38f4..6c8615f0ad 100644 --- a/testing/tests/dwi2fod +++ b/testing/tests/dwi2fod @@ -1,5 +1,5 @@ -dwi2fod csd dwi.mif response.txt - | testing_diff_data - dwi2fod/out.mif -voxel 1e-5 -dwi2fod csd dwi.mif -mask mask.mif response.txt - | testing_diff_data - dwi2fod/out_mask.mif -voxel 1e-5 -dwi2fod csd dwi.mif response.txt -lmax 12 - | testing_diff_data - dwi2fod/out_lmax12.mif -voxel 1e-5 -dwi2fod msmt_csd dwi2fod/msmt/dwi.mif dwi2fod/msmt/wm.txt tmp_wm.mif dwi2fod/msmt/gm.txt tmp_gm.mif dwi2fod/msmt/csf.txt tmp_csf.mif && mrcat tmp_wm.mif tmp_gm.mif tmp_csf.mif - -axis 3 | testing_diff_data - dwi2fod/msmt/out.mif -voxel 1e-5 -dwi2fod msmt_csd dwi2fod/msmt/dwi.mif -mask dwi2fod/msmt/mask.mif dwi2fod/msmt/wm.txt tmp_wm_m.mif dwi2fod/msmt/gm.txt tmp_gm_m.mif dwi2fod/msmt/csf.txt tmp_csf_m.mif && mrcat tmp_wm_m.mif tmp_gm_m.mif tmp_csf_m.mif - -axis 3 | testing_diff_data - dwi2fod/msmt/out_masked.mif -voxel 1e-5 +dwi2fod csd dwi.mif response.txt - | testing_diff_image - dwi2fod/out.mif -voxel 1e-5 +dwi2fod csd dwi.mif -mask mask.mif response.txt - | testing_diff_image - dwi2fod/out_mask.mif -voxel 1e-5 +dwi2fod csd dwi.mif response.txt -lmax 12 - | testing_diff_image - dwi2fod/out_lmax12.mif -voxel 1e-5 +dwi2fod msmt_csd dwi2fod/msmt/dwi.mif dwi2fod/msmt/wm.txt tmp_wm.mif dwi2fod/msmt/gm.txt tmp_gm.mif dwi2fod/msmt/csf.txt tmp_csf.mif && mrcat tmp_wm.mif tmp_gm.mif tmp_csf.mif - -axis 3 | testing_diff_image - dwi2fod/msmt/out.mif -voxel 1e-5 +dwi2fod msmt_csd dwi2fod/msmt/dwi.mif -mask dwi2fod/msmt/mask.mif dwi2fod/msmt/wm.txt tmp_wm_m.mif dwi2fod/msmt/gm.txt tmp_gm_m.mif dwi2fod/msmt/csf.txt tmp_csf_m.mif && mrcat tmp_wm_m.mif tmp_gm_m.mif tmp_csf_m.mif - -axis 3 | testing_diff_image - dwi2fod/msmt/out_masked.mif -voxel 1e-5 diff --git a/testing/tests/dwi2mask b/testing/tests/dwi2mask index 2ffff09d96..79dc71f482 100644 --- a/testing/tests/dwi2mask +++ b/testing/tests/dwi2mask @@ -1 +1 @@ -dwi2mask dwi.mif - | testing_diff_data - dwi2mask/out.mif +dwi2mask dwi.mif - | testing_diff_image - dwi2mask/out.mif diff --git a/testing/tests/dwi2noise b/testing/tests/dwi2noise index 9926748743..64fcd5cf22 100644 --- a/testing/tests/dwi2noise +++ b/testing/tests/dwi2noise @@ -1 +1 @@ -dwi2noise dwi.mif - | testing_diff_data - noise.mif -frac 1e-5 +dwi2noise dwi.mif - | testing_diff_image - noise.mif -frac 1e-5 diff --git a/testing/tests/dwi2tensor b/testing/tests/dwi2tensor index 0af5b1cfbe..49352e5f3f 100644 --- a/testing/tests/dwi2tensor +++ b/testing/tests/dwi2tensor @@ -1,8 +1,8 @@ -dwi2tensor dwi.mif - | testing_diff_data - dwi2tensor/out.mif -frac 1e-5 -dwi2tensor dwi.mif -mask mask.mif - | testing_diff_data - dwi2tensor/out_mask.mif -frac 1e-5 -dwi2tensor dwi.mif -mask mask.mif -b0 - tmp_dt.mif | testing_diff_data - dwi2tensor/out_b0.mif -frac 1e-5 -dwi2tensor dwi.mif -mask mask.mif -iter 0 - | testing_diff_data - dwi2tensor/out_it0.mif -frac 1e-5 -dwi2tensor dwi.mif -mask mask.mif -iter 1 - | testing_diff_data - dwi2tensor/out_it1.mif -frac 1e-5 -dwi2tensor dwi.mif -mask mask.mif -iter 2 - | testing_diff_data - dwi2tensor/out_it2.mif -frac 1e-5 -dwi2tensor dwi.mif -mask mask.mif -iter 3 - | testing_diff_data - dwi2tensor/out_it3.mif -frac 1e-5 -dwi2tensor dwi.mif -mask mask.mif -iter 4 - | testing_diff_data - dwi2tensor/out_it4.mif -frac 1e-5 +dwi2tensor dwi.mif - | testing_diff_image - dwi2tensor/out.mif -frac 1e-5 +dwi2tensor dwi.mif -mask mask.mif - | testing_diff_image - dwi2tensor/out_mask.mif -frac 1e-5 +dwi2tensor dwi.mif -mask mask.mif -b0 - tmp_dt.mif | testing_diff_image - dwi2tensor/out_b0.mif -frac 1e-5 +dwi2tensor dwi.mif -mask mask.mif -iter 0 - | testing_diff_image - dwi2tensor/out_it0.mif -frac 1e-5 +dwi2tensor dwi.mif -mask mask.mif -iter 1 - | testing_diff_image - dwi2tensor/out_it1.mif -frac 1e-5 +dwi2tensor dwi.mif -mask mask.mif -iter 2 - | testing_diff_image - dwi2tensor/out_it2.mif -frac 1e-5 +dwi2tensor dwi.mif -mask mask.mif -iter 3 - | testing_diff_image - dwi2tensor/out_it3.mif -frac 1e-5 +dwi2tensor dwi.mif -mask mask.mif -iter 4 - | testing_diff_image - dwi2tensor/out_it4.mif -frac 1e-5 diff --git a/testing/tests/dwidenoise b/testing/tests/dwidenoise index f934ebc84a..f4c64f8147 100644 --- a/testing/tests/dwidenoise +++ b/testing/tests/dwidenoise @@ -1,6 +1,6 @@ -dwidenoise dwi.mif - | testing_diff_data - dwidenoise/dwi.mif -voxel 1e-4 -dwidenoise dwi.mif -mask mask.mif - | testing_diff_data - dwidenoise/masked.mif -voxel 1e-4 -dwidenoise dwi.mif -extent 3 - | testing_diff_data - dwidenoise/extent3.mif -voxel 1e-4 -dwidenoise dwi.mif -extent 5,3,1 - | testing_diff_data - dwidenoise/extent531.mif -voxel 1e-4 -dwidenoise dwi.mif -noise tmp-noise.mif - | testing_diff_data - dwidenoise/dwi.mif -voxel 1e-4 && testing_diff_data tmp-noise.mif dwidenoise/noise.mif -frac 1e-4 -dwidenoise dwi.mif -extent 3 -noise tmp-noise3.mif - | testing_diff_data - dwidenoise/extent3.mif -voxel 1e-4 && testing_diff_data tmp-noise3.mif dwidenoise/noise3.mif -frac 1e-4 +dwidenoise dwi.mif - | testing_diff_image - dwidenoise/dwi.mif -voxel 1e-4 +dwidenoise dwi.mif -mask mask.mif - | testing_diff_image - dwidenoise/masked.mif -voxel 1e-4 +dwidenoise dwi.mif -extent 3 - | testing_diff_image - dwidenoise/extent3.mif -voxel 1e-4 +dwidenoise dwi.mif -extent 5,3,1 - | testing_diff_image - dwidenoise/extent531.mif -voxel 1e-4 +dwidenoise dwi.mif -noise tmp-noise.mif - | testing_diff_image - dwidenoise/dwi.mif -voxel 1e-4 && testing_diff_image tmp-noise.mif dwidenoise/noise.mif -frac 1e-4 +dwidenoise dwi.mif -extent 3 -noise tmp-noise3.mif - | testing_diff_image - dwidenoise/extent3.mif -voxel 1e-4 && testing_diff_image tmp-noise3.mif dwidenoise/noise3.mif -frac 1e-4 diff --git a/testing/tests/dwiextract b/testing/tests/dwiextract index 4f6165ee37..8892befa69 100644 --- a/testing/tests/dwiextract +++ b/testing/tests/dwiextract @@ -1,2 +1,2 @@ -dwiextract dwi.mif - | testing_diff_data - dwiextract/out1.mif -dwiextract dwi.mif -bzero - | testing_diff_data - dwiextract/out2.mif +dwiextract dwi.mif - | testing_diff_image - dwiextract/out1.mif +dwiextract dwi.mif -bzero - | testing_diff_image - dwiextract/out2.mif diff --git a/testing/tests/fixel2sh b/testing/tests/fixel2sh index c36256ea3a..bc53f75cea 100644 --- a/testing/tests/fixel2sh +++ b/testing/tests/fixel2sh @@ -1 +1 @@ -fixel2sh fod2fixel/afd.msf - | testing_diff_data - fixel2sh/out.mif -voxel 1e-5 +fixel2sh fod2fixel/afd.msf - | testing_diff_image - fixel2sh/out.mif -voxel 1e-5 diff --git a/testing/tests/label2colour b/testing/tests/label2colour index 76efa9e26e..cc52610ae9 100644 --- a/testing/tests/label2colour +++ b/testing/tests/label2colour @@ -1,2 +1,2 @@ -label2colour labelconvert/in_fs.mif.gz -lut labelconvert/FreeSurferColorLUT.txt - | testing_diff_data - label2colour/out_fs.mif.gz -label2colour labelconvert/in_iit.mif.gz -lut labelconvert/LUT_GM.txt - | testing_diff_data - label2colour/out_iit.mif.gz +label2colour labelconvert/in_fs.mif.gz -lut labelconvert/FreeSurferColorLUT.txt - | testing_diff_image - label2colour/out_fs.mif.gz +label2colour labelconvert/in_iit.mif.gz -lut labelconvert/LUT_GM.txt - | testing_diff_image - label2colour/out_iit.mif.gz diff --git a/testing/tests/labelconvert b/testing/tests/labelconvert index 05c619d209..0c63643306 100644 --- a/testing/tests/labelconvert +++ b/testing/tests/labelconvert @@ -1,2 +1,2 @@ -labelconvert labelconvert/in_aal.mif.gz labelconvert/ROI_MNI_V4.txt labelconvert/aal.txt - | testing_diff_data - labelconvert/out_aal.mif.gz -labelconvert labelconvert/in_fs.mif.gz labelconvert/FreeSurferColorLUT.txt labelconvert/fs_default.txt - | testing_diff_data - labelconvert/out_fs.mif.gz +labelconvert labelconvert/in_aal.mif.gz labelconvert/ROI_MNI_V4.txt labelconvert/aal.txt - | testing_diff_image - labelconvert/out_aal.mif.gz +labelconvert labelconvert/in_fs.mif.gz labelconvert/FreeSurferColorLUT.txt labelconvert/fs_default.txt - | testing_diff_image - labelconvert/out_fs.mif.gz diff --git a/testing/tests/maskfilter b/testing/tests/maskfilter index c365695abf..ca749b368a 100644 --- a/testing/tests/maskfilter +++ b/testing/tests/maskfilter @@ -1,9 +1,9 @@ -maskfilter components_mask.mif connect - | testing_diff_data - maskfilter/out1.mif -maskfilter components_mask.mif connect -largest - | testing_diff_data - maskfilter/out2.mif -maskfilter components_mask.mif connect -connectivity - | testing_diff_data - maskfilter/out3.mif -maskfilter components_mask.mif connect -connectivity -largest - | testing_diff_data - maskfilter/out4.mif -maskfilter components_mask.mif connect -axes 0,2 - | testing_diff_data - maskfilter/out5.mif -maskfilter components_mask.mif connect -axes 0,2 -connectivity - | testing_diff_data - maskfilter/out6.mif -maskfilter mask.mif dilate -npass 2 - | testing_diff_data - maskfilter/out7.mif -maskfilter mask.mif erode -npass 2 - | testing_diff_data - maskfilter/out8.mif -maskfilter mask.mif median -extent 3,3,3 - | testing_diff_data - maskfilter/out9.mif +maskfilter components_mask.mif connect - | testing_diff_image - maskfilter/out1.mif +maskfilter components_mask.mif connect -largest - | testing_diff_image - maskfilter/out2.mif +maskfilter components_mask.mif connect -connectivity - | testing_diff_image - maskfilter/out3.mif +maskfilter components_mask.mif connect -connectivity -largest - | testing_diff_image - maskfilter/out4.mif +maskfilter components_mask.mif connect -axes 0,2 - | testing_diff_image - maskfilter/out5.mif +maskfilter components_mask.mif connect -axes 0,2 -connectivity - | testing_diff_image - maskfilter/out6.mif +maskfilter mask.mif dilate -npass 2 - | testing_diff_image - maskfilter/out7.mif +maskfilter mask.mif erode -npass 2 - | testing_diff_image - maskfilter/out8.mif +maskfilter mask.mif median -extent 3,3,3 - | testing_diff_image - maskfilter/out9.mif diff --git a/testing/tests/mesh2pve b/testing/tests/mesh2pve index e2cd812c5e..e77626209b 100644 --- a/testing/tests/mesh2pve +++ b/testing/tests/mesh2pve @@ -1 +1 @@ -mesh2pve meshconvert/in.vtk meshconvert/image.mif.gz - | testing_diff_data - mesh2pve/out.mif.gz -abs 1.5e-3 +mesh2pve meshconvert/in.vtk meshconvert/image.mif.gz - | testing_diff_image - mesh2pve/out.mif.gz -abs 1.5e-3 diff --git a/testing/tests/mrcalc b/testing/tests/mrcalc index 0e3a7d9130..d19128aa8a 100644 --- a/testing/tests/mrcalc +++ b/testing/tests/mrcalc @@ -1,4 +1,4 @@ -mrcalc mrcalc/in.mif 2 -mult -neg -exp 10 -add - | testing_diff_data - mrcalc/out1.mif -frac 1e-5 -mrcalc mrcalc/in.mif 1.224 -div -cos mrcalc/in.mif -abs -sqrt -log -atanh -sub - | testing_diff_data - mrcalc/out2.mif -frac 1e-5 -mrcalc mrcalc/in.mif 0.2 -gt mrcalc/in.mif mrcalc/in.mif -1.123 -mult 0.9324 -add -exp -neg -if - | testing_diff_data - mrcalc/out3.mif -frac 1e-5 -mrcalc mrcalc/in.mif 0+1i -mult -exp mrcalc/in.mif -mult 1.34+5.12i -mult - | testing_diff_data - mrcalc/out4.mif -frac 1e-5 +mrcalc mrcalc/in.mif 2 -mult -neg -exp 10 -add - | testing_diff_image - mrcalc/out1.mif -frac 1e-5 +mrcalc mrcalc/in.mif 1.224 -div -cos mrcalc/in.mif -abs -sqrt -log -atanh -sub - | testing_diff_image - mrcalc/out2.mif -frac 1e-5 +mrcalc mrcalc/in.mif 0.2 -gt mrcalc/in.mif mrcalc/in.mif -1.123 -mult 0.9324 -add -exp -neg -if - | testing_diff_image - mrcalc/out3.mif -frac 1e-5 +mrcalc mrcalc/in.mif 0+1i -mult -exp mrcalc/in.mif -mult 1.34+5.12i -mult - | testing_diff_image - mrcalc/out4.mif -frac 1e-5 diff --git a/testing/tests/mrcat b/testing/tests/mrcat index db453117d9..b035887ffd 100644 --- a/testing/tests/mrcat +++ b/testing/tests/mrcat @@ -1,5 +1,5 @@ -mrconvert dwi.mif tmp-[].mif && testing_diff_data tmp-[].mif dwi.mif -mrcat tmp-??.mif - | testing_diff_data - dwi.mif -mrcat tmp-[0:4].mif tmp-[5:20].mif tmp-[21:67] - | testing_diff_data - dwi.mif -mrcat tmp-[0:4].mif tmp-[5:20].mif tmp-21.mif tmp-[22:67] - | testing_diff_data - dwi.mif -mrcat tmp-[0:16].mif tmp-[17:33].mif tmp-[34:50].mif tmp-[51:67].mif -axis 4 - | testing_diff_data - mrcat/out.mif +mrconvert dwi.mif tmp-[].mif && testing_diff_image tmp-[].mif dwi.mif +mrcat tmp-??.mif - | testing_diff_image - dwi.mif +mrcat tmp-[0:4].mif tmp-[5:20].mif tmp-[21:67] - | testing_diff_image - dwi.mif +mrcat tmp-[0:4].mif tmp-[5:20].mif tmp-21.mif tmp-[22:67] - | testing_diff_image - dwi.mif +mrcat tmp-[0:16].mif tmp-[17:33].mif tmp-[34:50].mif tmp-[51:67].mif -axis 4 - | testing_diff_image - mrcat/out.mif diff --git a/testing/tests/mrconvert b/testing/tests/mrconvert index 6736ee2781..d493116480 100644 --- a/testing/tests/mrconvert +++ b/testing/tests/mrconvert @@ -1,11 +1,11 @@ -mrconvert mrconvert/in.mif - | testing_diff_data - mrconvert/in.mif -mrconvert mrconvert/in.mif -stride 2,-1,3 - | testing_diff_data - mrconvert/in.mif -mrconvert mrconvert/in.mif -datatype cfloat32 - | testing_diff_data - mrconvert/in.mif -mrconvert mrconvert/in.mif -stride 3,1,2 tmp.mif && testing_diff_data tmp.mif mrconvert/in.mif -mrconvert mrconvert/in.mif -stride 1,-3,2 -datatype float32be tmp.mih && testing_diff_data tmp.mih mrconvert/in.mif -mrconvert mrconvert/in.mif -datatype float32 tmp.mif.gz && testing_diff_data tmp.mif.gz mrconvert/in.mif -mrconvert mrconvert/in.mif tmp.nii && testing_diff_data tmp.nii mrconvert/in.mif -mrconvert mrconvert/in.mif -datatype float32 tmp.nii.gz && testing_diff_data tmp.nii.gz mrconvert/in.mif -mrconvert mrconvert/in.mif -stride 3,2,1 tmp.mgh && testing_diff_data tmp.mgh mrconvert/in.mif -mrconvert mrconvert/in.mif -stride 1,3,2 -datatype int16 tmp.mgz && testing_diff_data tmp.mgz mrconvert/in.mif -mrconvert dwi.mif tmp-[].mif; testing_diff_data dwi.mif tmp-[].mif +mrconvert mrconvert/in.mif - | testing_diff_image - mrconvert/in.mif +mrconvert mrconvert/in.mif -stride 2,-1,3 - | testing_diff_image - mrconvert/in.mif +mrconvert mrconvert/in.mif -datatype cfloat32 - | testing_diff_image - mrconvert/in.mif +mrconvert mrconvert/in.mif -stride 3,1,2 tmp.mif && testing_diff_image tmp.mif mrconvert/in.mif +mrconvert mrconvert/in.mif -stride 1,-3,2 -datatype float32be tmp.mih && testing_diff_image tmp.mih mrconvert/in.mif +mrconvert mrconvert/in.mif -datatype float32 tmp.mif.gz && testing_diff_image tmp.mif.gz mrconvert/in.mif +mrconvert mrconvert/in.mif tmp.nii && testing_diff_image tmp.nii mrconvert/in.mif +mrconvert mrconvert/in.mif -datatype float32 tmp.nii.gz && testing_diff_image tmp.nii.gz mrconvert/in.mif +mrconvert mrconvert/in.mif -stride 3,2,1 tmp.mgh && testing_diff_image tmp.mgh mrconvert/in.mif +mrconvert mrconvert/in.mif -stride 1,3,2 -datatype int16 tmp.mgz && testing_diff_image tmp.mgz mrconvert/in.mif +mrconvert dwi.mif tmp-[].mif; testing_diff_image dwi.mif tmp-[].mif diff --git a/testing/tests/mrcrop b/testing/tests/mrcrop index 8cd678f955..47c60be7e8 100644 --- a/testing/tests/mrcrop +++ b/testing/tests/mrcrop @@ -1,2 +1,2 @@ -mrcrop noise.mif -mask mrcrop/mask.mif - | testing_diff_data - mrcrop/out.mif -mrcrop noise.mif -axis 2 1 3 -axis 0 3 5 - | testing_diff_data - mrcrop/out2.mif +mrcrop noise.mif -mask mrcrop/mask.mif - | testing_diff_image - mrcrop/out.mif +mrcrop noise.mif -axis 2 1 3 -axis 0 3 5 - | testing_diff_image - mrcrop/out2.mif diff --git a/testing/tests/mrfilter b/testing/tests/mrfilter index 101703f699..35f0e6c981 100644 --- a/testing/tests/mrfilter +++ b/testing/tests/mrfilter @@ -1,15 +1,15 @@ -mrfilter dwi.mif smooth - | testing_diff_data - mrfilter/out1.mif -frac 1e-5 -mrfilter dwi.mif smooth -stdev 1.5,1.5,1.5 - | testing_diff_data - mrfilter/out2.mif -frac 1e-5 -mrfilter dwi.mif smooth -stdev 1.4 - | testing_diff_data - mrfilter/out3.mif -frac 1e-5 -mrfilter dwi.mif smooth -fwhm 3,3,3 - | testing_diff_data - mrfilter/out4.mif -frac 1e-5 -mrfilter dwi.mif smooth -stdev 1.4 -extent 5 - | testing_diff_data - mrfilter/out5.mif -frac 1e-5 -mrfilter dwi.mif median - | testing_diff_data - mrfilter/out6.mif -mrfilter dwi.mif median -extent 5,3,1 - | testing_diff_data - mrfilter/out7.mif -frac 1e-5 -mrfilter dwi.mif median -extent 5 - | testing_diff_data - mrfilter/out8.mif -frac 1e-5 -mrfilter dwi.mif smooth -stdev 1.5,2.5,3.5 - | testing_diff_data - mrfilter/out13.mif -frac 1e-5 -mrfilter dwi.mif gradient -stdev 1.5,2.5,3.5 - | testing_diff_data - mrfilter/out14.mif -frac 1e-5 -mrfilter dwi.mif gradient -stdev 1.5,2.5,3.5 -magnitude - | testing_diff_data - mrfilter/out15.mif -frac 1e-5 -mrfilter dwi.mif gradient -stdev 1.5,2.5,3.5 -scanner - | testing_diff_data - mrfilter/out16.mif -frac 1e-5 -mrfilter dwi.mif gradient -stdev 1.5,2.5,3.5 -magnitude -scanner - | testing_diff_data - mrfilter/out17.mif -frac 1e-5 -testing_diff_data $(mrmath mrfilter/out14.mif mrfilter/out14.mif product - | mrmath - sum -axis 3 - | mrconvert - -axes 0,1,2,4 - ) $(mrmath mrfilter/out15.mif mrfilter/out15.mif product - ) -frac 1e-5 -testing_diff_data $(mrmath mrfilter/out16.mif mrfilter/out16.mif product - | mrmath - sum -axis 3 - | mrconvert - -axes 0,1,2,4 - ) $(mrmath mrfilter/out17.mif mrfilter/out17.mif product - ) -frac 1e-5 \ No newline at end of file +mrfilter dwi.mif smooth - | testing_diff_image - mrfilter/out1.mif -frac 1e-5 +mrfilter dwi.mif smooth -stdev 1.5,1.5,1.5 - | testing_diff_image - mrfilter/out2.mif -frac 1e-5 +mrfilter dwi.mif smooth -stdev 1.4 - | testing_diff_image - mrfilter/out3.mif -frac 1e-5 +mrfilter dwi.mif smooth -fwhm 3,3,3 - | testing_diff_image - mrfilter/out4.mif -frac 1e-5 +mrfilter dwi.mif smooth -stdev 1.4 -extent 5 - | testing_diff_image - mrfilter/out5.mif -frac 1e-5 +mrfilter dwi.mif median - | testing_diff_image - mrfilter/out6.mif +mrfilter dwi.mif median -extent 5,3,1 - | testing_diff_image - mrfilter/out7.mif -frac 1e-5 +mrfilter dwi.mif median -extent 5 - | testing_diff_image - mrfilter/out8.mif -frac 1e-5 +mrfilter dwi.mif smooth -stdev 1.5,2.5,3.5 - | testing_diff_image - mrfilter/out13.mif -frac 1e-5 +mrfilter dwi.mif gradient -stdev 1.5,2.5,3.5 - | testing_diff_image - mrfilter/out14.mif -frac 1e-5 +mrfilter dwi.mif gradient -stdev 1.5,2.5,3.5 -magnitude - | testing_diff_image - mrfilter/out15.mif -frac 1e-5 +mrfilter dwi.mif gradient -stdev 1.5,2.5,3.5 -scanner - | testing_diff_image - mrfilter/out16.mif -frac 1e-5 +mrfilter dwi.mif gradient -stdev 1.5,2.5,3.5 -magnitude -scanner - | testing_diff_image - mrfilter/out17.mif -frac 1e-5 +testing_diff_image $(mrmath mrfilter/out14.mif mrfilter/out14.mif product - | mrmath - sum -axis 3 - | mrconvert - -axes 0,1,2,4 - ) $(mrmath mrfilter/out15.mif mrfilter/out15.mif product - ) -frac 1e-5 +testing_diff_image $(mrmath mrfilter/out16.mif mrfilter/out16.mif product - | mrmath - sum -axis 3 - | mrconvert - -axes 0,1,2,4 - ) $(mrmath mrfilter/out17.mif mrfilter/out17.mif product - ) -frac 1e-5 \ No newline at end of file diff --git a/testing/tests/mrmath b/testing/tests/mrmath index b6a70927dc..a0fdd1b504 100644 --- a/testing/tests/mrmath +++ b/testing/tests/mrmath @@ -1,4 +1,4 @@ -mrmath dwi.mif mean -axis 3 - | testing_diff_data - mrmath/out1.mif -frac 1e-5 -mrmath dwi.mif rms -axis 3 - | testing_diff_data - mrmath/out2.mif -frac 1e-5 -mrmath dwi.mif norm -axis 3 - | mrcalc - 0.12126781251816648 -mult - | testing_diff_data - mrmath/out2.mif -frac 1e-5 -mrconvert dwi.mif tmp-[].mif; mrmath tmp-??.mif median - | testing_diff_data - mrmath/out3.mif -frac 1e-5 \ No newline at end of file +mrmath dwi.mif mean -axis 3 - | testing_diff_image - mrmath/out1.mif -frac 1e-5 +mrmath dwi.mif rms -axis 3 - | testing_diff_image - mrmath/out2.mif -frac 1e-5 +mrmath dwi.mif norm -axis 3 - | mrcalc - 0.12126781251816648 -mult - | testing_diff_image - mrmath/out2.mif -frac 1e-5 +mrconvert dwi.mif tmp-[].mif; mrmath tmp-??.mif median - | testing_diff_image - mrmath/out3.mif -frac 1e-5 \ No newline at end of file diff --git a/testing/tests/mrpad b/testing/tests/mrpad index 5f4c917910..e87de28994 100644 --- a/testing/tests/mrpad +++ b/testing/tests/mrpad @@ -1,4 +1,4 @@ -mrpad dwi.mif -uniform 3 - | testing_diff_data - mrpad/out.mif -mrpad dwi.mif -axis 0 1 2 - | testing_diff_data - mrpad/out2.mif -mrpad dwi.mif -axis 0 1 2 -uniform 3 - | testing_diff_data - mrpad/out3.mif -mrpad dwi.mif -axis 0 1 2 -axis 1 0 2 - | testing_diff_data - mrpad/out4.mif +mrpad dwi.mif -uniform 3 - | testing_diff_image - mrpad/out.mif +mrpad dwi.mif -axis 0 1 2 - | testing_diff_image - mrpad/out2.mif +mrpad dwi.mif -axis 0 1 2 -uniform 3 - | testing_diff_image - mrpad/out3.mif +mrpad dwi.mif -axis 0 1 2 -axis 1 0 2 - | testing_diff_image - mrpad/out4.mif diff --git a/testing/tests/mrresize b/testing/tests/mrresize index cf3e50e02c..10784007cd 100644 --- a/testing/tests/mrresize +++ b/testing/tests/mrresize @@ -1,8 +1,8 @@ -mrresize dwi.mif -scale 0.4 -datatype uint16 - | testing_diff_data - mrresize/out1.mif -mrresize dwi.mif -scale 1.6 -datatype float32 - | testing_diff_data - mrresize/out2.mif -voxel 1e-5 -mrresize dwi.mif -scale 1.6 -datatype float32 -interp linear - | testing_diff_data - mrresize/out3.mif -voxel 1e-5 -mrresize dwi.mif -scale 1.6 -datatype float32 -interp sinc - | testing_diff_data - mrresize/out4.mif -voxel 1e-4 -mrresize dwi.mif -scale 2 -datatype float32 -interp nearest - | testing_diff_data - mrresize/out5.mif -voxel 1e-5 -mrresize dwi.mif -scale 1.9,0.5,1.3 -datatype float32 - | testing_diff_data - mrresize/out6.mif -voxel 1e-5 -mrresize dwi.mif -size 13,7,15 -datatype float32 - | testing_diff_data - mrresize/out7.mif -voxel 1e-5 -mrresize dwi.mif -vox 1.5,2.6,1.8 -datatype float32 - | testing_diff_data - mrresize/out8.mif -voxel 1e-5 +mrresize dwi.mif -scale 0.4 -datatype uint16 - | testing_diff_image - mrresize/out1.mif +mrresize dwi.mif -scale 1.6 -datatype float32 - | testing_diff_image - mrresize/out2.mif -voxel 1e-5 +mrresize dwi.mif -scale 1.6 -datatype float32 -interp linear - | testing_diff_image - mrresize/out3.mif -voxel 1e-5 +mrresize dwi.mif -scale 1.6 -datatype float32 -interp sinc - | testing_diff_image - mrresize/out4.mif -voxel 1e-4 +mrresize dwi.mif -scale 2 -datatype float32 -interp nearest - | testing_diff_image - mrresize/out5.mif -voxel 1e-5 +mrresize dwi.mif -scale 1.9,0.5,1.3 -datatype float32 - | testing_diff_image - mrresize/out6.mif -voxel 1e-5 +mrresize dwi.mif -size 13,7,15 -datatype float32 - | testing_diff_image - mrresize/out7.mif -voxel 1e-5 +mrresize dwi.mif -vox 1.5,2.6,1.8 -datatype float32 - | testing_diff_image - mrresize/out8.mif -voxel 1e-5 diff --git a/testing/tests/mrthreshold b/testing/tests/mrthreshold index 39264879f7..a6e4979e88 100644 --- a/testing/tests/mrthreshold +++ b/testing/tests/mrthreshold @@ -1,10 +1,10 @@ -mrthreshold dwi_mean.mif - | testing_diff_data - mrthreshold/default.mif -mrthreshold dwi_mean.mif - -invert | testing_diff_data - mrthreshold/invert.mif -mrthreshold dwi_mean.mif - -abs 100 | testing_diff_data - mrthreshold/abs100.mif -mrthreshold dwi_mean.mif - -histogram | testing_diff_data - mrthreshold/hist.mif -mrthreshold dwi_mean.mif - -percentile 50 | testing_diff_data - mrthreshold/perc50.mif -mrthreshold dwi_mean.mif - -top 20 | testing_diff_data - mrthreshold/top20.mif -mrthreshold dwi_mean.mif - -bottom 20 | testing_diff_data - mrthreshold/bottom20.mif -mrthreshold dwi_mean.mif - -toppercent 10 | testing_diff_data - mrthreshold/top10perc.mif -mrthreshold dwi_mean.mif - -bottompercent 10 | testing_diff_data - mrthreshold/bottom10perc.mif -mrthreshold dwi_mean.mif - -mask mask.mif | testing_diff_data - mrthreshold/masked.mif +mrthreshold dwi_mean.mif - | testing_diff_image - mrthreshold/default.mif +mrthreshold dwi_mean.mif - -invert | testing_diff_image - mrthreshold/invert.mif +mrthreshold dwi_mean.mif - -abs 100 | testing_diff_image - mrthreshold/abs100.mif +mrthreshold dwi_mean.mif - -histogram | testing_diff_image - mrthreshold/hist.mif +mrthreshold dwi_mean.mif - -percentile 50 | testing_diff_image - mrthreshold/perc50.mif +mrthreshold dwi_mean.mif - -top 20 | testing_diff_image - mrthreshold/top20.mif +mrthreshold dwi_mean.mif - -bottom 20 | testing_diff_image - mrthreshold/bottom20.mif +mrthreshold dwi_mean.mif - -toppercent 10 | testing_diff_image - mrthreshold/top10perc.mif +mrthreshold dwi_mean.mif - -bottompercent 10 | testing_diff_image - mrthreshold/bottom10perc.mif +mrthreshold dwi_mean.mif - -mask mask.mif | testing_diff_image - mrthreshold/masked.mif diff --git a/testing/tests/mrtransform b/testing/tests/mrtransform index 06e544e5d4..66d732969c 100644 --- a/testing/tests/mrtransform +++ b/testing/tests/mrtransform @@ -1,7 +1,7 @@ -mrtransform moving.mif.gz -template template.mif.gz -linear moving2template.txt - | testing_diff_data - mrtransform/out.mif.gz -frac 1e-5 -mrtransform moving.mif.gz -linear moving2template.txt - | testing_diff_data - mrtransform/out2.mif.gz -frac 1e-5 -mrtransform moving.mif.gz -replace moving2template.txt - | testing_diff_data - mrtransform/out3.mif.gz -frac 1e-5 -mrtransform template.mif.gz -template moving.mif.gz -interp linear -inverse -linear moving2template.txt - | testing_diff_data - mrtransform/out4.mif.gz -frac 1e-5 -mrtransform dwi_mean.mif -flip 0 - | testing_diff_data - mrtransform/out5.mif -frac 1e-5 -mrtransform dwi.mif -identity - | testing_diff_data - mrtransform/out6.mif -mrinfo dwi.mif -transform > tmp.txt && mrtransform -replace tmp.txt dwi.mif - | mrtransform dwi.mif -template - - | testing_diff_data - dwi.mif -abs 1e-4 \ No newline at end of file +mrtransform moving.mif.gz -template template.mif.gz -linear moving2template.txt - | testing_diff_image - mrtransform/out.mif.gz -frac 1e-5 +mrtransform moving.mif.gz -linear moving2template.txt - | testing_diff_image - mrtransform/out2.mif.gz -frac 1e-5 +mrtransform moving.mif.gz -replace moving2template.txt - | testing_diff_image - mrtransform/out3.mif.gz -frac 1e-5 +mrtransform template.mif.gz -template moving.mif.gz -interp linear -inverse -linear moving2template.txt - | testing_diff_image - mrtransform/out4.mif.gz -frac 1e-5 +mrtransform dwi_mean.mif -flip 0 - | testing_diff_image - mrtransform/out5.mif -frac 1e-5 +mrtransform dwi.mif -identity - | testing_diff_image - mrtransform/out6.mif +mrinfo dwi.mif -transform > tmp.txt && mrtransform -replace tmp.txt dwi.mif - | mrtransform dwi.mif -template - - | testing_diff_image - dwi.mif -abs 1e-4 \ No newline at end of file diff --git a/testing/tests/peaks2amp b/testing/tests/peaks2amp index fd3686c361..87a54da6df 100644 --- a/testing/tests/peaks2amp +++ b/testing/tests/peaks2amp @@ -1 +1 @@ -peaks2amp sh2peaks/out.mif - | testing_diff_data - peaks2amp/out.mif -frac 1e-5 +peaks2amp sh2peaks/out.mif - | testing_diff_image - peaks2amp/out.mif -frac 1e-5 diff --git a/testing/tests/sh2amp b/testing/tests/sh2amp index 178fa9aa6a..21974416e3 100644 --- a/testing/tests/sh2amp +++ b/testing/tests/sh2amp @@ -1,3 +1,3 @@ -amp2sh dwi.mif - | sh2amp - sh2amp/dir20.txt - | testing_diff_data - sh2amp/out.mif -voxel 1e-5 -amp2sh dwi.mif - | sh2amp - sh2amp/grad.txt -gradient - | testing_diff_data - sh2amp/out_grad.mif -voxel 1e-5 -amp2sh dwi.mif - | sh2amp - sh2amp/dir20.txt -nonnegative - | testing_diff_data - sh2amp/out_nonneg.mif -voxel 1e-5 +amp2sh dwi.mif - | sh2amp - sh2amp/dir20.txt - | testing_diff_image - sh2amp/out.mif -voxel 1e-5 +amp2sh dwi.mif - | sh2amp - sh2amp/grad.txt -gradient - | testing_diff_image - sh2amp/out_grad.mif -voxel 1e-5 +amp2sh dwi.mif - | sh2amp - sh2amp/dir20.txt -nonnegative - | testing_diff_image - sh2amp/out_nonneg.mif -voxel 1e-5 diff --git a/testing/tests/sh2power b/testing/tests/sh2power index 90269337c0..dfc0e1128f 100644 --- a/testing/tests/sh2power +++ b/testing/tests/sh2power @@ -1 +1 @@ -#dwiextract dwi.mif - | mrcalc - 2 -pow - | mrmath -axis 3 - mean tmp1.mif && amp2sh dwi.mif - | sh2power - - | testing_diff_data - tmp1.mif -frac 0.1 +#dwiextract dwi.mif - | mrcalc - 2 -pow - | mrmath -axis 3 - mean tmp1.mif && amp2sh dwi.mif - | sh2power - - | testing_diff_image - tmp1.mif -frac 0.1 diff --git a/testing/tests/shbasis b/testing/tests/shbasis index 7a8a9c57b1..71cb32a720 100644 --- a/testing/tests/shbasis +++ b/testing/tests/shbasis @@ -1,4 +1,4 @@ -mrconvert dwi2fod/out.mif tmp.mif -force && shbasis tmp.mif -convert new && testing_diff_data tmp.mif dwi2fod/out.mif -frac 1e-5 -mrconvert dwi2fod/out.mif tmp.mif -force && shbasis tmp.mif -convert old && testing_diff_data tmp.mif shbasis/old.mif -frac 1e-5 -mrconvert shbasis/old.mif tmp.mif -force && shbasis tmp.mif -convert new && testing_diff_data tmp.mif dwi2fod/out.mif -frac 1e-5 -mrconvert shbasis/old.mif tmp.mif -force && shbasis tmp.mif -convert old && testing_diff_data tmp.mif shbasis/old.mif -frac 1e-5 +mrconvert dwi2fod/out.mif tmp.mif -force && shbasis tmp.mif -convert new && testing_diff_image tmp.mif dwi2fod/out.mif -frac 1e-5 +mrconvert dwi2fod/out.mif tmp.mif -force && shbasis tmp.mif -convert old && testing_diff_image tmp.mif shbasis/old.mif -frac 1e-5 +mrconvert shbasis/old.mif tmp.mif -force && shbasis tmp.mif -convert new && testing_diff_image tmp.mif dwi2fod/out.mif -frac 1e-5 +mrconvert shbasis/old.mif tmp.mif -force && shbasis tmp.mif -convert old && testing_diff_image tmp.mif shbasis/old.mif -frac 1e-5 diff --git a/testing/tests/shconv b/testing/tests/shconv index ecacd2ed72..b7d663a867 100644 --- a/testing/tests/shconv +++ b/testing/tests/shconv @@ -1,2 +1,2 @@ -shconv dwi2fod/out.mif response.txt - | testing_diff_data - shconv/out.mif -frac 1e-5 -shconv dwi2fod/out.mif -mask mask.mif response.txt - | testing_diff_data - shconv/out_mask.mif -frac 1e-5 +shconv dwi2fod/out.mif response.txt - | testing_diff_image - shconv/out.mif -frac 1e-5 +shconv dwi2fod/out.mif -mask mask.mif response.txt - | testing_diff_image - shconv/out_mask.mif -frac 1e-5 diff --git a/testing/tests/tckgen b/testing/tests/tckgen index 1f6abfc1fc..b9dff180e9 100644 --- a/testing/tests/tckgen +++ b/testing/tests/tckgen @@ -1,10 +1,10 @@ -tckgen SIFT_phantom/dwi.mif -algo seedtest -seed_sphere 0,0,4,4 -number 50000 tmp.tck -force && tckmap tmp.tck -template SIFT_phantom/dwi.mif - | testing_diff_data - tckgen/seed_sphere.mif -abs 1000 -tckgen SIFT_phantom/dwi.mif -algo seedtest -seed_image SIFT_phantom/mask.mif -number 3888 tmp.tck -force && tckmap tmp.tck -template SIFT_phantom/dwi.mif - | testing_diff_data - tckgen/SIFT_phantom_seeds.mif -abs 26 -tckgen SIFT_phantom/dwi.mif -algo seedtest -seed_random_per_voxel SIFT_phantom/mask.mif 27 tmp.tck -force && tckmap tmp.tck -template SIFT_phantom/dwi.mif - | testing_diff_data - tckgen/SIFT_phantom_seeds.mif -tckgen SIFT_phantom/dwi.mif -algo seedtest -seed_grid_per_voxel SIFT_phantom/mask.mif 3 tmp.tck -force && tckmap tmp.tck -template SIFT_phantom/dwi.mif - | testing_diff_data - tckgen/SIFT_phantom_seeds.mif +tckgen SIFT_phantom/dwi.mif -algo seedtest -seed_sphere 0,0,4,4 -number 50000 tmp.tck -force && tckmap tmp.tck -template SIFT_phantom/dwi.mif - | testing_diff_image - tckgen/seed_sphere.mif -abs 1000 +tckgen SIFT_phantom/dwi.mif -algo seedtest -seed_image SIFT_phantom/mask.mif -number 3888 tmp.tck -force && tckmap tmp.tck -template SIFT_phantom/dwi.mif - | testing_diff_image - tckgen/SIFT_phantom_seeds.mif -abs 26 +tckgen SIFT_phantom/dwi.mif -algo seedtest -seed_random_per_voxel SIFT_phantom/mask.mif 27 tmp.tck -force && tckmap tmp.tck -template SIFT_phantom/dwi.mif - | testing_diff_image - tckgen/SIFT_phantom_seeds.mif +tckgen SIFT_phantom/dwi.mif -algo seedtest -seed_grid_per_voxel SIFT_phantom/mask.mif 3 tmp.tck -force && tckmap tmp.tck -template SIFT_phantom/dwi.mif - | testing_diff_image - tckgen/SIFT_phantom_seeds.mif tckgen SIFT_phantom/peaks.mif -algo fact -seed_rejection tckgen/rejection_seed.mif -number 5000 -minlength 4 -mask SIFT_phantom/mask.mif tmp.tck -force && tckmap tmp.tck -template SIFT_phantom/dwi.mif tmp.mif -force && mrstats tmp.mif -mask SIFT_phantom/upper.mif -output mean > tmp1.txt && mrstats tmp.mif -mask SIFT_phantom/lower.mif -output mean > tmp2.txt && testing_diff_matrix tmp1.txt tmp2.txt -abs 30 -tckgen SIFT_phantom/fods.mif -algo ifod1 -seed_image SIFT_phantom/mask.mif -act SIFT_phantom/5tt.mif -number 5000 tmp.tck -force && tckmap tmp.tck -template tckgen/act_terminations.mif -ends_only - | mrthreshold - - -abs 0.5 | testing_diff_data - tckgen/act_terminations.mif -tckgen SIFT_phantom/dwi.mif -algo seedtest -seed_gmwmi 5tt2gmwmi/out.mif -act SIFT_phantom/5tt.mif -number 100000 tmp.tck -force && tckmap tmp.tck -template tckgen/gmwmi_seeds.mif - | mrthreshold - - -abs 0.5 | testing_diff_data - tckgen/gmwmi_seeds.mif +tckgen SIFT_phantom/fods.mif -algo ifod1 -seed_image SIFT_phantom/mask.mif -act SIFT_phantom/5tt.mif -number 5000 tmp.tck -force && tckmap tmp.tck -template tckgen/act_terminations.mif -ends_only - | mrthreshold - - -abs 0.5 | testing_diff_image - tckgen/act_terminations.mif +tckgen SIFT_phantom/dwi.mif -algo seedtest -seed_gmwmi 5tt2gmwmi/out.mif -act SIFT_phantom/5tt.mif -number 100000 tmp.tck -force && tckmap tmp.tck -template tckgen/gmwmi_seeds.mif - | mrthreshold - - -abs 0.5 | testing_diff_image - tckgen/gmwmi_seeds.mif tckgen SIFT_phantom/peaks.mif -algo fact -seed_dynamic SIFT_phantom/fods.mif -mask SIFT_phantom/mask.mif -number 10000 -minlength 4 -initdir 1,0,0 tmp.tck -nthreads 0 -force && tckmap tmp.tck -template SIFT_phantom/dwi.mif tmp.mif -force && mrstats tmp.mif -mask SIFT_phantom/upper.mif -output mean > tmp1.txt && mrstats tmp.mif -mask SIFT_phantom/lower.mif -output mean > tmp2.txt && testing_diff_matrix tmp1.txt tmp2.txt -abs 50 tckgen SIFT_phantom/fods.mif -algo ifod2 -seed_image SIFT_phantom/mask.mif -mask SIFT_phantom/mask.mif -minlength 4 -number 100 tmp.tck -force tckgen SIFT_phantom/fods.mif -algo sd_stream -seed_image SIFT_phantom/mask.mif -mask SIFT_phantom/mask.mif -minlength 4 -number 100 -initdir 1,0,0 tmp.tck -force diff --git a/testing/tests/tckmap b/testing/tests/tckmap index cc4f0e9107..452287253b 100644 --- a/testing/tests/tckmap +++ b/testing/tests/tckmap @@ -1,4 +1,4 @@ -tckmap tckmap/in.tck -template dwi.mif - | testing_diff_data - tckmap/tdi.mif.gz -abs 1.5 -tckmap tckmap/in.tck -vox 1 - | testing_diff_data - tckmap/tdi_vox1.mif.gz -abs 1.5 -tckmap tckmap/in.tck -template dwi.mif -dec - | testing_diff_data - tckmap/tdi_color.mif.gz -abs 1.5 -tckmap tckmap/in.tck -tod 6 -template dwi.mif - | testing_diff_data - tckmap/tod_lmax6.mif.gz -voxel 1e-4 +tckmap tckmap/in.tck -template dwi.mif - | testing_diff_image - tckmap/tdi.mif.gz -abs 1.5 +tckmap tckmap/in.tck -vox 1 - | testing_diff_image - tckmap/tdi_vox1.mif.gz -abs 1.5 +tckmap tckmap/in.tck -template dwi.mif -dec - | testing_diff_image - tckmap/tdi_color.mif.gz -abs 1.5 +tckmap tckmap/in.tck -tod 6 -template dwi.mif - | testing_diff_image - tckmap/tod_lmax6.mif.gz -voxel 1e-4 diff --git a/testing/tests/tensor2metric b/testing/tests/tensor2metric index d5b0c39513..76ffd39a7f 100644 --- a/testing/tests/tensor2metric +++ b/testing/tests/tensor2metric @@ -1,8 +1,8 @@ -tensor2metric tensor2metric/dt.mif -fa - | testing_diff_data - tensor2metric/fa.mif -frac 1e-5 -tensor2metric tensor2metric/dt.mif -adc - | testing_diff_data - tensor2metric/adc.mif -frac 1e-5 -tensor2metric tensor2metric/dt.mif -num 1 -value - | testing_diff_data - tensor2metric/eigval1.mif -frac 1e-4 -tensor2metric tensor2metric/dt.mif -num 2 -value - | testing_diff_data - tensor2metric/eigval2.mif -frac 1e-4 -tensor2metric tensor2metric/dt.mif -num 3 -value - | testing_diff_data - tensor2metric/eigval3.mif -frac 1e-4 -tensor2metric tensor2metric/dt.mif -num 1 -modulate none -vector - | mrcalc - tensor2metric/eigvec1.mif -multiply - | mrmath -axis 3 - sum - | mrcalc - -abs - | testing_diff_data - tensor2metric/ones.mif -frac 1e-5 -tensor2metric tensor2metric/dt.mif -num 2 -modulate none -vector - | mrcalc - tensor2metric/eigvec2.mif -multiply - | mrmath -axis 3 - sum - | mrcalc - -abs - | testing_diff_data - tensor2metric/ones.mif -frac 1e-5 -tensor2metric tensor2metric/dt.mif -num 3 -modulate none -vector - | mrcalc - tensor2metric/eigvec3.mif -multiply - | mrmath -axis 3 - sum - | mrcalc - -abs - | testing_diff_data - tensor2metric/ones.mif -frac 1e-5 +tensor2metric tensor2metric/dt.mif -fa - | testing_diff_image - tensor2metric/fa.mif -frac 1e-5 +tensor2metric tensor2metric/dt.mif -adc - | testing_diff_image - tensor2metric/adc.mif -frac 1e-5 +tensor2metric tensor2metric/dt.mif -num 1 -value - | testing_diff_image - tensor2metric/eigval1.mif -frac 1e-4 +tensor2metric tensor2metric/dt.mif -num 2 -value - | testing_diff_image - tensor2metric/eigval2.mif -frac 1e-4 +tensor2metric tensor2metric/dt.mif -num 3 -value - | testing_diff_image - tensor2metric/eigval3.mif -frac 1e-4 +tensor2metric tensor2metric/dt.mif -num 1 -modulate none -vector - | mrcalc - tensor2metric/eigvec1.mif -multiply - | mrmath -axis 3 - sum - | mrcalc - -abs - | testing_diff_image - tensor2metric/ones.mif -frac 1e-5 +tensor2metric tensor2metric/dt.mif -num 2 -modulate none -vector - | mrcalc - tensor2metric/eigvec2.mif -multiply - | mrmath -axis 3 - sum - | mrcalc - -abs - | testing_diff_image - tensor2metric/ones.mif -frac 1e-5 +tensor2metric tensor2metric/dt.mif -num 3 -modulate none -vector - | mrcalc - tensor2metric/eigvec3.mif -multiply - | mrmath -axis 3 - sum - | mrcalc - -abs - | testing_diff_image - tensor2metric/ones.mif -frac 1e-5 diff --git a/testing/tests/warpcorrect b/testing/tests/warpcorrect index 8dbb2c84c5..66365b9643 100644 --- a/testing/tests/warpcorrect +++ b/testing/tests/warpcorrect @@ -1 +1 @@ -warpcorrect warp.mif - | testing_diff_data - warpcorrect/out.mif -frac 1e-5 +warpcorrect warp.mif - | testing_diff_image - warpcorrect/out.mif -frac 1e-5 diff --git a/testing/tests/warpinit b/testing/tests/warpinit index fb2f5470c2..5b767605a0 100644 --- a/testing/tests/warpinit +++ b/testing/tests/warpinit @@ -1 +1 @@ -warpinit dwi.mif - | testing_diff_data - warpinit/out.mif -frac 1e-5 +warpinit dwi.mif - | testing_diff_image - warpinit/out.mif -frac 1e-5 From 5bfe1da7e8641c002d25652a6e539d29ad82f3aa Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 23 Sep 2016 13:19:49 +1000 Subject: [PATCH 151/723] Fix compilation on gcc 6.2.1 Didn't like implicit copy-constructor of a class containing a vector of std::unique_ptr's. --- src/dwi/tractography/seeding/list.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dwi/tractography/seeding/list.h b/src/dwi/tractography/seeding/list.h index 15f51f8c0b..63d315871c 100644 --- a/src/dwi/tractography/seeding/list.h +++ b/src/dwi/tractography/seeding/list.h @@ -43,6 +43,8 @@ namespace MR total_volume (0.0), total_count (0) { } + List (const List&) = delete; + void add (Base* const in); void clear(); From 09daffdefe1563c12e91972142c5065320927c87 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 23 Sep 2016 13:20:24 +1000 Subject: [PATCH 152/723] tcksample: Ability to disable interpolation --- cmd/tcksample.cpp | 346 ++++++++++++++++--------- docs/reference/commands/dwidenoise.rst | 4 +- docs/reference/commands/tcksample.rst | 7 + 3 files changed, 230 insertions(+), 127 deletions(-) diff --git a/cmd/tcksample.cpp b/cmd/tcksample.cpp index 1c65c9b420..78195b8a31 100644 --- a/cmd/tcksample.cpp +++ b/cmd/tcksample.cpp @@ -24,6 +24,8 @@ #include "dwi/tractography/mapping/mapper.h" #include "file/ofstream.h" #include "file/path.h" +#include "interp/linear.h" +#include "interp/nearest.h" #include "math/median.h" @@ -36,6 +38,8 @@ using namespace App; enum stat_tck { MEAN, MEDIAN, MIN, MAX, NONE }; const char* statistics[] = { "mean", "median", "min", "max", nullptr }; +enum interp_type { NEAREST, LINEAR, PRECISE }; + void usage () { @@ -60,6 +64,8 @@ void usage () "(options are: " + join(statistics, ",") + ")") + Argument ("statistic").type_choice (statistics) + + Option ("nointerp", "do not use trilinear interpolation when sampling image values") + + 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)") @@ -77,6 +83,12 @@ void usage () // (this would supersede fixel2tsf when used without -precise or -stat_tck options) // (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"; + } @@ -113,125 +125,56 @@ class TDI : public Image -// Guarantees thread-safety -class Sampler { +template +class SamplerNonPrecise { public: - Sampler (Image& image, const stat_tck statistic, const bool precise, std::unique_ptr& precalc_tdi) : - interp ((!precise && !precalc_tdi) ? new Interp::Linear> (image) : nullptr), - mapper ((precise || precalc_tdi) ? new DWI::Tractography::Mapping::TrackMapperBase (image) : nullptr), - image ((precise || precalc_tdi) ? new Image (image) : nullptr), - tdi (precalc_tdi ? new TDI (*precalc_tdi) : nullptr), + SamplerNonPrecise (Image& image, const stat_tck statistic, MR::copy_ptr& precalc_tdi) : + interp (image), + mapper (precalc_tdi ? new DWI::Tractography::Mapping::TrackMapperBase (image) : nullptr), + tdi (precalc_tdi), statistic (statistic) { - assert (!(statistic == stat_tck::NONE && precise)); if (mapper) - mapper->set_use_precise_mapping (precise); + mapper->set_use_precise_mapping (false); } bool operator() (DWI::Tractography::Streamline<>& tck, std::pair& out) { assert (statistic != stat_tck::NONE); out.first = tck.index; - value_type sum_lengths = value_type(0); - // Only if _not_ using precise mapping, and _not_ using a pre-calculated TDI - // (in the latter case, the mapper will still be used, just without the precise mapping; - // each traversed voxel will return a length of 1) - if (interp) { - - std::pair 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.second[i] * length; - sum_lengths += length; - } - out.second = sum_lengths ? (integral / sum_lengths) : 0.0; - } else { - sum_lengths = tck.calc_length(); - if (statistic == MEDIAN) { - // Don't bother with a weighted median here - std::vector data; - data.assign (values.second.data(), values.second.data() + values.second.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.second[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.second[i]); - } else { - assert (0); - } + std::pair 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.second[i] * length; + sum_lengths += length; } - + out.second = sum_lengths ? (integral / sum_lengths) : 0.0; } else { - - 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; - }; - std::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; - } + if (statistic == MEDIAN) { + // Don't bother with a weighted median here + std::vector data; + data.assign (values.second.data(), values.second.data() + values.second.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.second[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.second[i]); } else { assert (0); } @@ -245,12 +188,11 @@ class Sampler { bool operator() (const DWI::Tractography::Streamline<>& tck, std::pair& out) { - assert (interp); out.first = tck.index; out.second.resize (tck.size()); for (size_t i = 0; i != tck.size(); ++i) { - if (interp->scanner (tck[i])) - out.second[i] = interp->value(); + if (interp.scanner (tck[i])) + out.second[i] = interp.value(); else out.second[i] = std::numeric_limits::quiet_NaN(); } @@ -258,9 +200,108 @@ class Sampler { } private: - MR::copy_ptr>> interp; + Interp interp; + std::shared_ptr mapper; + MR::copy_ptr tdi; + const stat_tck statistic; + + value_type get_tdi_multiplier (const DWI::Tractography::Mapping::Voxel& v) + { + if (!tdi) + 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, MR::copy_ptr& 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.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; + }; + std::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); + } + + if (!std::isfinite (out.second)) + out.second = NaN; + + return true; + } + + + private: + Image image; std::shared_ptr mapper; - MR::copy_ptr> image; MR::copy_ptr tdi; const stat_tck statistic; @@ -367,6 +408,49 @@ class Receiver_NoStatistic : private ReceiverBase + +template +void execute_nostat (DWI::Tractography::Reader& reader, + const DWI::Tractography::Properties& properties, + const size_t num_tracks, + Image& image, + const std::string& path) +{ + MR::copy_ptr no_tdi; + SamplerNonPrecise sampler (image, stat_tck::NONE, no_tdi); + Receiver_NoStatistic receiver (path, num_tracks, properties); + DWI::Tractography::Streamline tck; + std::pair values; + size_t counter = 0; + while (reader (tck)) { + sampler (tck, values); + receiver (values); + ++counter; + } + if (counter != num_tracks) + WARN ("Expected " + str(num_tracks) + " tracks based on header; read " + str(counter)); +} + +template +void execute (DWI::Tractography::Reader& reader, + const size_t num_tracks, + Image& image, + const stat_tck statistic, + MR::copy_ptr& tdi, + const std::string& path) +{ + SamplerType sampler (image, statistic, tdi); + Receiver_Statistic receiver (num_tracks); + Thread::run_queue (reader, + Thread::batch (DWI::Tractography::Streamline()), + Thread::multi (sampler), + Thread::batch (std::pair()), + receiver); + receiver.save (path); +} + + + void run () { DWI::Tractography::Properties properties; @@ -376,19 +460,25 @@ void run () 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(); + if (nointerp && precise) + 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"]); - if (statistic == stat_tck::NONE && precise) + if (statistic == stat_tck::NONE && interp == interp_type::PRECISE) throw Exception ("Precise streamline mapping may only be used with per-streamline statistics"); - std::unique_ptr tdi; + MR::copy_ptr tdi; 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 (precise); + mapper.set_use_precise_mapping (interp == interp_type::PRECISE); tdi.reset (new TDI (H, num_tracks)); Thread::run_queue (tdi_reader, Thread::batch (DWI::Tractography::Streamline()), @@ -398,25 +488,29 @@ void run () tdi->done(); } - Sampler sampler (image, statistic, precise, tdi); - if (statistic == stat_tck::NONE) { - Receiver_NoStatistic receiver (argument[2], num_tracks, properties); - DWI::Tractography::Streamline tck; - std::pair values; - while (reader (tck)) { - sampler (tck, values); - receiver (values); + 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"); } } else { - Receiver_Statistic receiver (num_tracks); - Thread::run_queue (reader, - Thread::batch (DWI::Tractography::Streamline()), - Thread::multi (sampler), - Thread::batch (std::pair()), - receiver); - receiver.save (argument[2]); + 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; + } } - } diff --git a/docs/reference/commands/dwidenoise.rst b/docs/reference/commands/dwidenoise.rst index fef943fb34..ecc9e671b9 100644 --- a/docs/reference/commands/dwidenoise.rst +++ b/docs/reference/commands/dwidenoise.rst @@ -55,7 +55,9 @@ Standard options References ^^^^^^^^^^ -Veraart, J.; Fieremans, E. & Novikov, D.S. Diffusion MRI noise mapping using random matrix theory Magn. Res. Med., 2016, early view, doi: 10.1002/mrm.26059 +Veraart, J.; Novikov, D.S.; Christiaens, D.; Ades-aron, B.; Sijbers, J. & Fieremans, E. Denoising of diffusion MRI using random matrix theory. NeuroImage, 2016, in press, doi: 10.1016/j.neuroimage.2016.08.016 + +Veraart, J.; Fieremans, E. & Novikov, D.S. Diffusion MRI noise mapping using random matrix theory. Magn. Res. Med., 2016, early view, doi: 10.1002/mrm.26059 -------------- diff --git a/docs/reference/commands/tcksample.rst b/docs/reference/commands/tcksample.rst index 12a46a4a5a..8d6ca391a1 100644 --- a/docs/reference/commands/tcksample.rst +++ b/docs/reference/commands/tcksample.rst @@ -26,6 +26,8 @@ Options - **-stat_tck statistic** compute some statistic from the values along each streamline (options are: mean,median,min,max) +- **-nointerp** do not use trilinear interpolation when sampling image values + - **-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) - **-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) @@ -49,6 +51,11 @@ Standard options - **-version** display version information and exit. +References +^^^^^^^^^^ + +* If using -precise option: Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. SIFT: Spherical-deconvolution informed filtering of tractograms. NeuroImage, 2013, 67, 298-312 + -------------- From 6b9e792bd4f450f121348ac13d626d48e27da686 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Fri, 23 Sep 2016 16:18:38 +1000 Subject: [PATCH 153/723] Added tests for enw fixel2sh, fixel2tsf, and fod2fixel. Mode some minor changes to fixel helpers --- cmd/fixel2sh.cpp | 2 +- cmd/fixel2tsf.cpp | 2 +- cmd/fixel2voxel.cpp | 3 +- cmd/fixelcfestats.cpp | 18 ++--- cmd/fixelcorrespondence.cpp | 7 +- cmd/fixelreorient.cpp | 2 +- cmd/voxel2fixel.cpp | 43 +++++++----- cmd/warp2metric.cpp | 10 +-- docs/reference/commands/voxel2fixel.rst | 7 +- lib/fixel_format/helpers.h | 82 +++++++++++++++------- src/gui/mrview/tool/vector/fixelfolder.cpp | 2 +- testing/data | 2 +- testing/tests/fixel2sh | 3 +- testing/tests/fixel2tsf | 2 +- testing/tests/fod2fixel | 8 ++- 15 files changed, 111 insertions(+), 82 deletions(-) diff --git a/cmd/fixel2sh.cpp b/cmd/fixel2sh.cpp index 9d530e78dd..ef4a70d4d7 100644 --- a/cmd/fixel2sh.cpp +++ b/cmd/fixel2sh.cpp @@ -57,7 +57,7 @@ void run () Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_folder (argument[0])); auto in_index_image =in_index_header.get_image(); - auto in_directions_image = FixelFormat::find_directions_header (FixelFormat::get_fixel_folder (argument[0]), in_index_header).get_image().with_direct_io(); + auto in_directions_image = FixelFormat::find_directions_header (FixelFormat::get_fixel_folder (argument[0])).get_image().with_direct_io(); size_t lmax = 8; auto opt = get_options ("lmax"); diff --git a/cmd/fixel2tsf.cpp b/cmd/fixel2tsf.cpp index 468a59e76e..e36a4c4f58 100644 --- a/cmd/fixel2tsf.cpp +++ b/cmd/fixel2tsf.cpp @@ -72,7 +72,7 @@ void run () Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_folder (argument[0])); auto in_index_image = in_index_header.get_image(); - auto in_directions_image = FixelFormat::find_directions_header (FixelFormat::get_fixel_folder (argument[0]), in_index_header).get_image().with_direct_io(); + auto in_directions_image = FixelFormat::find_directions_header (FixelFormat::get_fixel_folder (argument[0])).get_image().with_direct_io(); DWI::Tractography::Properties properties; DWI::Tractography::Reader reader (argument[1], properties); diff --git a/cmd/fixel2voxel.cpp b/cmd/fixel2voxel.cpp index 9f4be28e60..d8ea8154eb 100644 --- a/cmd/fixel2voxel.cpp +++ b/cmd/fixel2voxel.cpp @@ -446,8 +446,7 @@ void run () if (op == 10 || op == 11 || op == 13) // dec or split_dir in_directions = FixelFormat::find_directions_header ( - FixelFormat::get_fixel_folder (in_data.name()), - in_index_header).get_image().with_direct_io(); + FixelFormat::get_fixel_folder (in_data.name())).get_image().with_direct_io(); Image in_vol; auto opt = get_options ("weighted"); diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index ab5663936b..de7a3bda21 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -150,27 +150,19 @@ void run() { const std::string input_fixel_folder = argument[0]; Header index_header = FixelFormat::find_index_header (input_fixel_folder); - std::cout << index_header.valid() << std::endl; - std::cout << index_header << std::endl; auto index_image = index_header.get_image(); - TRACE; - uint32_t num_fixels = FixelFormat::get_number_of_fixels (index_header); + const uint32_t num_fixels = FixelFormat::get_number_of_fixels (index_header); CONSOLE ("number of fixels: " + str(num_fixels)); std::vector positions (num_fixels); std::vector directions (num_fixels); const std::string output_fixel_folder = argument[5]; - FixelFormat::check_fixel_folder (output_fixel_folder, true); + FixelFormat::copy_index_and_directions_file (input_fixel_folder, output_fixel_folder); { - auto output_index_image = Image::create (Path::join (output_fixel_folder, Path::basename (index_image.name())), index_image); - threaded_copy_with_progress_message ("copying fixel index into output folder", index_image, output_index_image, 0, std::numeric_limits::max(), 2); - auto directions_data = FixelFormat::find_directions_header (input_fixel_folder, index_image).get_image().with_direct_io(); - auto output_directions_data = Image::create(Path::join (output_fixel_folder, Path::basename (directions_data.name())), directions_data); - threaded_copy_with_progress_message ("copying fixel directions into output folder", directions_data, output_directions_data); - + auto directions_data = FixelFormat::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) { @@ -178,7 +170,7 @@ void run() { index_image.index(3) = 1; uint32_t offset = index_image.value(); size_t fixel_index = 0; - for (auto f = FixelFormat::FixelLoop (index_image) (directions_data, output_directions_data); f; ++f, ++fixel_index) { + for (auto f = FixelFormat::FixelLoop (index_image) (directions_data); f; ++f, ++fixel_index) { directions[offset + fixel_index] = directions_data.row(1).cast(); positions[offset + fixel_index] = image_transform.voxel2scanner * vox; } @@ -307,7 +299,6 @@ void run() { output_header.keyval()["connectivity threshold"] = str(connectivity_threshold); output_header.keyval()["smoothing FWHM"] = str(smooth_std_dev * 2.3548); - // Load input data matrix_type data (num_fixels, identifiers.size()); { @@ -333,7 +324,6 @@ void run() { value += subject_data_vector[it->first] * it->second; data (fixel, subject) = value; } - progress++; } } diff --git a/cmd/fixelcorrespondence.cpp b/cmd/fixelcorrespondence.cpp index b7cfd31f10..f4b37c84e8 100644 --- a/cmd/fixelcorrespondence.cpp +++ b/cmd/fixelcorrespondence.cpp @@ -57,7 +57,7 @@ void run () throw Exception ("please input the specific fixel data file to be converted (not the fixel folder)"); auto subject_index = FixelFormat::find_index_header (FixelFormat::get_fixel_folder (input_file)).get_image(); - auto subject_directions = FixelFormat::find_directions_header (FixelFormat::get_fixel_folder (input_file), subject_index).get_image().with_direct_io(); + auto subject_directions = FixelFormat::find_directions_header (FixelFormat::get_fixel_folder (input_file)).get_image().with_direct_io(); if (input_file == subject_directions.name()) throw Exception ("input fixel data file cannot be the directions file"); @@ -66,12 +66,11 @@ void run () FixelFormat::check_fixel_size (subject_index, subject_data); auto template_index = FixelFormat::find_index_header (argument[1]).get_image(); - auto template_directions = FixelFormat::find_directions_header (argument[1], template_index).get_image().with_direct_io(); + auto template_directions = FixelFormat::find_directions_header (argument[1]).get_image().with_direct_io(); check_dimensions (subject_index, template_index); std::string output_fixel_folder = argument[2]; - FixelFormat::copy_index_file (argument[1], output_fixel_folder); - FixelFormat::copy_directions_file (argument[1], output_fixel_folder); + FixelFormat::copy_index_and_directions_file (argument[1], output_fixel_folder); Header output_data_header (template_directions); output_data_header.size(1) = 1; diff --git a/cmd/fixelreorient.cpp b/cmd/fixelreorient.cpp index 8ed794cb8f..9eace9beb2 100644 --- a/cmd/fixelreorient.cpp +++ b/cmd/fixelreorient.cpp @@ -66,7 +66,7 @@ void run () Image input_directions_image; std::string output_directions_filename; { - auto tmp = FixelFormat::find_directions_header (input_fixel_folder, input_index_image).get_image(); + auto tmp = FixelFormat::find_directions_header (input_fixel_folder).get_image(); input_directions_image = Image::scratch(tmp); threaded_copy (tmp, input_directions_image); output_directions_filename = Path::basename(tmp.name()); diff --git a/cmd/voxel2fixel.cpp b/cmd/voxel2fixel.cpp index 5b06eef340..99333dd922 100644 --- a/cmd/voxel2fixel.cpp +++ b/cmd/voxel2fixel.cpp @@ -18,14 +18,14 @@ #include "progressbar.h" #include "algo/loop.h" #include "image.h" -#include "sparse/fixel_metric.h" -#include "sparse/keys.h" -#include "sparse/image.h" + +#include "fixel_format/helpers.h" +#include "fixel_format/keys.h" +#include "fixel_format/loop.h" using namespace MR; using namespace App; -using Sparse::FixelMetric; void usage () { @@ -37,24 +37,31 @@ void usage () ARGUMENTS + Argument ("image_in", "the input image.").type_image_in () - + Argument ("fixel_in", "the input fixel image.").type_image_in () - + Argument ("fixel_out", "the output fixel image.").type_image_out (); + + Argument ("fixel_in", "the input fixel folder. Used to define the fixels and their directions").type_text () + + Argument ("fixel_out", "the output fixel folder. This can be the same as the input folder if desired").type_text () + + Argument ("fixel_out", "the name of the fixel data image.").type_image_out (); } void run () { auto scalar = Image::open (argument[0]); - auto fixel_header = Header::open (argument[1]); - check_dimensions (scalar, fixel_header, 0, 3); - Sparse::Image fixel_template (argument[1]); - Sparse::Image output (argument[2], fixel_header); - - for (auto i = Loop ("mapping voxel scalar values to fixels", 0, 3)(scalar, fixel_template, output); i; ++i) { - output.value().set_size (fixel_template.value().size()); - for (size_t f = 0; f != fixel_template.value().size(); ++f) { - output.value()[f] = fixel_template.value()[f]; - output.value()[f].value = scalar.value(); - } - } + std::string input_fixel_folder = argument[1]; + FixelFormat::check_fixel_folder (input_fixel_folder); + auto input_fixel_index = FixelFormat::find_index_header (input_fixel_folder).get_image(); + check_dimensions (scalar, input_fixel_index, 0, 3); + + std::string output_fixel_folder = argument[2]; + if (input_fixel_folder != output_fixel_folder) + FixelFormat::copy_index_and_directions_file (input_fixel_folder, output_fixel_folder); + + auto output_fixel_data = Image::create (argument[3], FixelFormat::data_header_from_index (input_fixel_index)); + +// for (auto i = Loop ("mapping voxel scalar values to fixels", 0, 3)(scalar, input_fixel_index); i; ++i) { +// output.value().set_size (fixel_template.value().size()); +// for (size_t f = 0; f != fixel_template.value().size(); ++f) { +// output.value()[f] = fixel_template.value()[f]; +// output.value()[f].value = scalar.value(); +// } +// } } diff --git a/cmd/warp2metric.cpp b/cmd/warp2metric.cpp index 79c013ecc7..dfd0d8b6ce 100644 --- a/cmd/warp2metric.cpp +++ b/cmd/warp2metric.cpp @@ -70,13 +70,13 @@ void run () Image fixel_template_index; Image fixel_template_directions; - Image fc_output_data; + Image fc_output_data; auto opt = get_options ("fc"); if (opt.size()) { std::string template_fixel_folder (opt[0][0]); fixel_template_index = FixelFormat::find_index_header (template_fixel_folder).get_image(); - fixel_template_directions = FixelFormat::find_directions_header (template_fixel_folder, fixel_template_index).get_image().with_direct_io(); + fixel_template_directions = FixelFormat::find_directions_header (template_fixel_folder).get_image().with_direct_io(); std::string output_fixel_folder (opt[0][1]); if (template_fixel_folder != output_fixel_folder) { @@ -89,11 +89,7 @@ void run () for (auto l = Loop (fixel_template_index, 0, 3) (fixel_template_index); l; ++l) num_fixels += fixel_template_index.value(); - Header output_header (fixel_template_directions); - output_header.size(1) = 1; - output_header.datatype() = DataType::Float32; - output_header.datatype().set_byte_order_native(); - fc_output_data = Image::create (Path::join(output_fixel_folder, opt[0][2]), output_header); + fc_output_data = Image::create (Path::join (output_fixel_folder, opt[0][2]), FixelFormat::data_header_from_index (fixel_template_index)); } diff --git a/docs/reference/commands/voxel2fixel.rst b/docs/reference/commands/voxel2fixel.rst index 3b2ad6387a..387ef22355 100644 --- a/docs/reference/commands/voxel2fixel.rst +++ b/docs/reference/commands/voxel2fixel.rst @@ -8,11 +8,12 @@ Synopsis :: - voxel2fixel [ options ] image_in fixel_in fixel_out + voxel2fixel [ options ] image_in fixel_in fixel_out fixel_out - *image_in*: the input image. -- *fixel_in*: the input fixel image. -- *fixel_out*: the output fixel image. +- *fixel_in*: the input fixel folder. Used to define the fixels and their directions +- *fixel_out*: the output fixel folder. This can be the same as the input folder if desired +- *fixel_out*: the name of the fixel data image. Description ----------- diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index 7547101c44..0fea995cb4 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -32,7 +32,7 @@ namespace MR namespace FixelFormat { - inline bool is_index_image (const Header& in) + FORCE_INLINE bool is_index_image (const Header& in) { bool is_index = false; if (in.ndim() == 4) { @@ -48,33 +48,42 @@ namespace MR } template - inline void check_index_image (const IndexHeaderType& index) + FORCE_INLINE void check_index_image (const IndexHeaderType& index) { if (!is_index_image (index)) throw InvalidImageException (index.name() + " is not a valid fixel index image. Image must be 4D with 2 volumes in the 4th dimension"); } - inline bool is_data_file (const Header& in) + FORCE_INLINE bool is_data_file (const Header& in) { return in.ndim() == 3 && in.size(2) == 1; } - inline bool is_directions_file (const Header& in) + FORCE_INLINE bool is_directions_file (const Header& in) { - std::string basename (Path::basename (in.name())); - return in.ndim() == 3 && in.size(1) == 3 && in.size(2) == 1 && (basename.substr(0, basename.find_last_of(".")) == "directions"); + bool is_directions = false; + if (in.ndim() == 3) { + if (in.size(1) == 3 && in.size(2) == 1) { + for (std::initializer_list::iterator it = FixelFormat::supported_fixel_formats.begin(); + it != FixelFormat::supported_fixel_formats.end(); ++it) { + if (Path::basename (in.name()) == "directions" + *it) + is_directions = true; + } + } + } + return is_directions; } - inline void check_data_file (const Header& in) + FORCE_INLINE void check_data_file (const Header& in) { if (!is_data_file (in)) throw InvalidImageException (in.name() + " is not a valid fixel data file. Expected a 3-dimensional image of size n x m x 1"); } - inline std::string get_fixel_folder (const std::string& fixel_file) { + FORCE_INLINE std::string get_fixel_folder (const std::string& fixel_file) { std::string fixel_folder = Path::dirname (fixel_file); // assume the user is running the command from within the fixel directory if (fixel_folder.empty()) @@ -84,7 +93,7 @@ namespace MR template - inline uint32_t get_number_of_fixels (IndexHeaderType& index_header) { + FORCE_INLINE uint32_t get_number_of_fixels (IndexHeaderType& index_header) { check_index_image (index_header); if (index_header.keyval().count (n_fixels_key)) { return std::stoul (index_header.keyval().at(n_fixels_key)); @@ -106,9 +115,8 @@ namespace MR } - template - inline bool fixels_match (const IndexHeaderType& index_header, const DataHeaderType& data_header) + FORCE_INLINE bool fixels_match (const IndexHeaderType& index_header, const DataHeaderType& data_header) { bool fixels_match (false); @@ -136,7 +144,7 @@ namespace MR } - inline void check_fixel_size (const Header& index_h, const Header& data_h) + FORCE_INLINE void check_fixel_size (const Header& index_h, const Header& data_h) { check_index_image (index_h); check_data_file (data_h); @@ -146,10 +154,10 @@ namespace MR } - inline void check_fixel_folder (const std::string &path, bool create_if_missing = false, bool check_if_empty = false) + FORCE_INLINE void check_fixel_folder (const std::string &path, bool create_if_missing = false, bool check_if_empty = false) { std::string path_temp = path; - // the user must be inside the fixel folder + // handle the use case when a fixel command is run from inside a fixel folder if (path.empty()) path_temp = Path::cwd(); @@ -167,7 +175,7 @@ namespace MR } - inline Header find_index_header (const std::string &fixel_folder_path) + FORCE_INLINE Header find_index_header (const std::string &fixel_folder_path) { Header header; check_fixel_folder (fixel_folder_path); @@ -189,7 +197,7 @@ namespace MR } - inline std::vector
find_data_headers (const std::string &fixel_folder_path, const Header &index_header, const bool include_directions = false) + FORCE_INLINE std::vector
find_data_headers (const std::string &fixel_folder_path, const Header &index_header, const bool include_directions = false) { check_index_image (index_header); std::vector
data_headers; @@ -212,13 +220,14 @@ namespace MR return data_headers; } - template - inline Header find_directions_header (const std::string &fixel_folder_path, const IndexHeaderType &index_header) + + FORCE_INLINE Header find_directions_header (const std::string fixel_folder_path) { bool directions_found (false); Header header; check_fixel_folder (fixel_folder_path); + Header index_header = FixelFormat::find_index_header (fixel_folder_path); auto dir_walker = Path::Dir (fixel_folder_path); std::string fname; @@ -246,9 +255,29 @@ namespace MR return header; } + //! Generate a header for a sparse data file (Nx1x1) using an index image as a template + template + FORCE_INLINE Header data_header_from_index (IndexHeaderType& index) { + Header header (index); + header.ndim() = 3; + header.size(0) = get_number_of_fixels (index); + header.size(1) = 1; + header.size(2) = 1; + header.datatype() = DataType::Float32; + header.datatype().set_byte_order_native(); + return header; + } + + //! Generate a header for a fixel directions data file (Nx3x1) using an index image as a template + template + FORCE_INLINE Header directions_header_from_index (IndexHeaderType& index) { + Header header = data_header_from_index (index); + header.size(1) = 3; + return header; + } //! Copy a file from one fixel folder into another. - inline void copy_fixel_file (const std::string& input_file_path, const std::string &output_folder) { + FORCE_INLINE void copy_fixel_file (const std::string& input_file_path, const std::string& output_folder) { check_fixel_folder (output_folder, true); std::string output_path = Path::join (output_folder, Path::basename (input_file_path)); Header input_header = Header::open (input_file_path); @@ -258,7 +287,7 @@ namespace MR } //! Copy the index file from one fixel folder into another - inline void copy_index_file (const std::string &input_folder, const std::string &output_folder) { + FORCE_INLINE void copy_index_file (const std::string& input_folder, const std::string& output_folder) { Header input_header = FixelFormat::find_index_header (input_folder); check_fixel_folder (output_folder, true); auto output_image = Image::create (Path::join (output_folder, Path::basename (input_header.name())), input_header); @@ -267,14 +296,19 @@ namespace MR } //! Copy the directions file from one fixel folder into another. - inline void copy_directions_file (const std::string &input_folder, const std::string &output_folder) { - Header input_header = FixelFormat::find_directions_header (input_folder, FixelFormat::find_index_header (input_folder)); - copy_fixel_file (input_header.name(), output_folder); + FORCE_INLINE void copy_directions_file (const std::string& input_folder, const std::string& output_folder) { + Header directions_header = FixelFormat::find_directions_header (input_folder); + copy_fixel_file (directions_header.name(), output_folder); + } + + FORCE_INLINE void copy_index_and_directions_file (const std::string& input_folder, const std::string &output_folder) { + copy_index_file (input_folder, output_folder); + copy_directions_file (input_folder, output_folder); } //! Copy all data files in a fixel folder into another folder. Data files do not include the index or directions file. - inline void copy_all_data_files (const std::string &input_folder, const std::string &output_folder) { + FORCE_INLINE void copy_all_data_files (const std::string &input_folder, const std::string &output_folder) { for (auto& input_header : FixelFormat::find_data_headers (input_folder, FixelFormat::find_index_header (input_folder))) copy_fixel_file (input_header.name(), output_folder); } diff --git a/src/gui/mrview/tool/vector/fixelfolder.cpp b/src/gui/mrview/tool/vector/fixelfolder.cpp index d5870e69a7..64a425dfe0 100644 --- a/src/gui/mrview/tool/vector/fixelfolder.cpp +++ b/src/gui/mrview/tool/vector/fixelfolder.cpp @@ -60,7 +60,7 @@ namespace MR // Load fixel direction images - auto directions_image = FixelFormat::find_directions_header (Path::dirname (fixel_data->name ()), *fixel_data).get_image ().with_direct_io (); + auto directions_image = FixelFormat::find_directions_header (Path::dirname (fixel_data->name())).get_image().with_direct_io (); directions_image.index (1) = 0; for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { fixel_data->index (3) = 0; diff --git a/testing/data b/testing/data index b6d0e018d0..d5b956641b 160000 --- a/testing/data +++ b/testing/data @@ -1 +1 @@ -Subproject commit b6d0e018d0bad96edec423b81ef1b006a94932fb +Subproject commit d5b956641b0f7aa1ec075dd78ae7983daae4dda8 diff --git a/testing/tests/fixel2sh b/testing/tests/fixel2sh index bc53f75cea..51a0c5c72f 100644 --- a/testing/tests/fixel2sh +++ b/testing/tests/fixel2sh @@ -1 +1,2 @@ -fixel2sh fod2fixel/afd.msf - | testing_diff_image - fixel2sh/out.mif -voxel 1e-5 +fixel2sh fixel_image/afd.mif - | testing_diff_image - fixel2sh/out.mif -voxel 1e-5 +fixel2sh fixel_image/afd.mif -lmax 4 - | testing_diff_image - fixel2sh/out1.mif -voxel 1e-5 diff --git a/testing/tests/fixel2tsf b/testing/tests/fixel2tsf index e7272ebad9..4742cb2ff7 100644 --- a/testing/tests/fixel2tsf +++ b/testing/tests/fixel2tsf @@ -1 +1 @@ -fixel2tsf afd.msf tracks.tck tmp.tsf -force && testing_diff_tsf tmp.tsf fixel2tsf/out.tsf -frac 1e-5 +fixel2tsf fixel_image/afd.mif tracks.tck tmp.tsf -force && testing_diff_tsf tmp.tsf fixel2tsf/out.tsf -frac 1e-5 diff --git a/testing/tests/fod2fixel b/testing/tests/fod2fixel index 5180bf09f7..677f6cb38f 100644 --- a/testing/tests/fod2fixel +++ b/testing/tests/fod2fixel @@ -1,3 +1,5 @@ -#fod2fixel dwi2fod/out.mif -afd tmp_afd.msf -peak tmp_peak.msf -disp tmp_disp.msf -force && testing_diff_fixel tmp_afd.msf fod2fixel/afd.msf 1e-2 && testing_diff_fixel tmp_peak.msf fod2fixel/peak.msf 1e-2 && testing_diff_fixel tmp_disp.msf fod2fixel/disp.msf 1e-2 -#fod2fixel dwi2fod/out.mif -mask mask.mif -afd tmp.msf -force && testing_diff_fixel tmp.msf fod2fixel/masked.msf 1e-2 -#fod2fixel dwi2fod/out.mif -fmls_no_thresholds -afd tmp.msf -force && testing_diff_fixel tmp.msf fod2fixel/nothresholds.msf 1e-2 +fod2fixel -nthread 1 fod.mif fod2fixel_tmp -afd afd.mif -peak peak.mif -disp disp.mif -force && testing_diff_fixel -frac 10e-5 fod2fixel/out1 fod2fixel_tmp && rm -rf fod2fixel_tmp + +fod2fixel -nthread 1 fod.mif -mask mask.mif fod2fixel_tmp -afd afd.mif -peak peak.mif -disp disp.mif -force && testing_diff_fixel -frac 10e-5 fod2fixel/out2 fod2fixel_tmp && rm -rf fod2fixel_tmp + +fod2fixel -nthread 1 -fmls_no_thresholds fod.mif fod2fixel_tmp -afd afd.mif -peak peak.mif -disp disp.mif -force && testing_diff_fixel -frac 10e-5 fod2fixel/out3 fod2fixel_tmp && rm -rf fod2fixel_tmp From d6ca8950fa36f64c26ce7287da5ffca339face82 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 26 Sep 2016 13:17:21 +1000 Subject: [PATCH 154/723] Move cartesian2spherical() and spherical2cartesian() to Math::Sphere Previously these were provided in lib/math/SH.h, which was an unusual header file to include in cases where only these conversions were required rather than any spherical harmonic functionalty. --- cmd/amp2response.cpp | 31 ++++----- cmd/dirsplit.cpp | 1 - cmd/fod2dec.cpp | 3 +- cmd/mrregister.cpp | 17 ++--- cmd/mrtransform.cpp | 3 +- lib/math/SH.h | 74 ---------------------- lib/math/sphere.h | 122 ++++++++++++++++++++++++++++++++++++ src/dwi/directions/file.cpp | 4 +- src/dwi/directions/file.h | 6 +- src/dwi/gradient.h | 3 +- 10 files changed, 160 insertions(+), 104 deletions(-) create mode 100644 lib/math/sphere.h diff --git a/cmd/amp2response.cpp b/cmd/amp2response.cpp index 76df5a5037..3e108157d2 100644 --- a/cmd/amp2response.cpp +++ b/cmd/amp2response.cpp @@ -30,6 +30,8 @@ #include "math/constrained_least_squares.h" #include "math/rng.h" +#include "math/sphere.h" +#include "math/SH.h" #include "math/ZSH.h" #include "dwi/gradient.h" @@ -116,7 +118,6 @@ void run () if (opt.size()) { dirs_azel = load_matrix (opt[0][0]); volumes = all_volumes (dirs_azel.rows()); - // TODO Switch between az/el pairs and XYZ triplets } else { auto hit = header.keyval().find ("directions"); if (hit != header.keyval().end()) { @@ -140,10 +141,13 @@ void run () } } - // TODO These functions should move - Eigen::MatrixXd dirs_cartesian = Math::SH::spherical2cartesian (dirs_azel); + Eigen::MatrixXd dirs_cartesian = Math::Sphere::spherical2cartesian (dirs_azel); - const size_t lmax = get_option_value ("lmax", Math::SH::LforN (volumes.size())); + // Because the amp->SH transform doesn't need to be applied per voxel, + // lmax is effectively unconstrained. Therefore generate response at + // lmax=8 regardless of number of input volumes, unless user + // explicitly requests something else + const size_t lmax = get_option_value ("lmax", 8); auto image = header.get_image(); auto mask = Image::open (argument[1]); @@ -175,7 +179,7 @@ void run () } // Grab the fibre direction - // TODO Eventually, it might be possible to optimise these fibre directions + // Eventually, it might be possible to optimise these fibre directions // during the response function fit; i.e. optimise (az,el) in each voxel // to minimise SSE compared to the current RF estimate Eigen::Vector3 fibre_dir; @@ -188,17 +192,17 @@ void run () Eigen::Matrix R = gen_rotation_matrix (fibre_dir); Eigen::Matrix rotated_dirs_cartesian (dirs_cartesian.rows(), 3); Eigen::Vector3 vec (3), rot (3); - for (size_t row = 0; row != size_t(dirs_azel.rows()); ++row) { + for (ssize_t row = 0; row != dirs_azel.rows(); ++row) { vec = dirs_cartesian.row (row); rot = R * vec; rotated_dirs_cartesian.row (row) = rot; } // Convert directions from Euclidean space to azimuth/elevation pairs - Eigen::MatrixXd rotated_dirs_azel = Math::SH::cartesian2spherical (rotated_dirs_cartesian); + Eigen::MatrixXd rotated_dirs_azel = Math::Sphere::cartesian2spherical (rotated_dirs_cartesian); // Constrain elevations to between 0 and pi/2 - for (size_t i = 0; i != size_t(rotated_dirs_azel.rows()); ++i) { + 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; @@ -209,7 +213,7 @@ void run () } #ifdef AMP2RESPONSE_PERVOXEL_IMAGES - // TODO For the sake of generating a figure, output the original and rotated signals to a dixel ODF image + // For the sake of generating a figure, output the original and rotated signals to a dixel ODF image Header rotated_header (header); rotated_header.size(0) = rotated_header.size(1) = rotated_header.size(2) = 1; rotated_header.size(3) = volumes.size(); @@ -220,7 +224,7 @@ void run () rotated_grad.block<1,3>(i, 0) = rotated_dirs_cartesian.row(i); rotated_grad(i, 3) = 1000.0; } - rotated_header.set_DW_scheme (rotated_grad); + DWI::set_DW_scheme (rotated_header, rotated_grad); Image out_rotated = Image::create ("rotated_amps_" + str(sf_counter) + ".mif", rotated_header); Image out_nonrotated = Image::create ("nonrotated_amps_" + str(sf_counter) + ".mif", nonrotated_header); out_rotated.index(0) = out_rotated.index(1) = out_rotated.index(2) = 0; @@ -230,7 +234,7 @@ void run () out_rotated.index(3) = i; out_rotated.value() = image.value(); } - for (size_t i = 0; i != header.size(3); ++i) { + for (ssize_t i = 0; i != header.size(3); ++i) { image.index(3) = out_nonrotated.index(3) = i; out_nonrotated.value() = image.value(); } @@ -262,7 +266,7 @@ void run () #endif // Generate the constraint matrix - // We are going to both constrain the amplitudes to be positive, and constrain the derivatives to be positive + // 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; els.resize (num_angles_constraint+1); @@ -284,8 +288,7 @@ void run () Eigen::VectorXd rf; const size_t niter = solver (rf, cat_data); - CONSOLE ("Response function [" + str(rf.transpose()) + " ] solved after " + str(niter) + " iterations from " + str(sf_counter) + " voxels"); + INFO ("Response function [" + str(rf.transpose()) + " ] solved after " + str(niter) + " iterations from " + str(sf_counter) + " voxels"); save_vector (rf, argument[3]); - } diff --git a/cmd/dirsplit.cpp b/cmd/dirsplit.cpp index ea8454bbeb..510dd84f49 100644 --- a/cmd/dirsplit.cpp +++ b/cmd/dirsplit.cpp @@ -17,7 +17,6 @@ #include "command.h" #include "progressbar.h" #include "math/rng.h" -#include "math/SH.h" #include "thread.h" #include "dwi/directions/file.h" diff --git a/cmd/fod2dec.cpp b/cmd/fod2dec.cpp index 30a0f55240..d91ffa858d 100644 --- a/cmd/fod2dec.cpp +++ b/cmd/fod2dec.cpp @@ -20,6 +20,7 @@ #include "progressbar.h" #include "algo/threaded_loop.h" #include "image.h" +#include "math/sphere.h" #include "math/SH.h" #include "dwi/directions/predefined.h" #include "filter/reslice.h" @@ -92,7 +93,7 @@ class DecTransform { DecTransform (int lmax, const Eigen::Matrix& dirs, double thresh) : sht (Math::SH::init_transform(dirs, lmax)), - decs (Math::SH::spherical2cartesian(dirs).cwiseAbs()), + decs (Math::Sphere::spherical2cartesian(dirs).cwiseAbs()), thresh (thresh) { } }; diff --git a/cmd/mrregister.cpp b/cmd/mrregister.cpp index 72b6f95fda..03716cfdca 100644 --- a/cmd/mrregister.cpp +++ b/cmd/mrregister.cpp @@ -27,8 +27,9 @@ #include "registration/transform/affine.h" #include "registration/transform/rigid.h" #include "dwi/directions/predefined.h" -#include "math/SH.h" #include "math/average_space.h" +#include "math/SH.h" +#include "math/sphere.h" using namespace MR; @@ -165,7 +166,7 @@ void run () Eigen::MatrixXd directions_cartesian; opt = get_options ("directions"); if (opt.size()) - directions_cartesian = Math::SH::spherical2cartesian (load_matrix (opt[0][0])).transpose(); + directions_cartesian = Math::Sphere::spherical2cartesian (load_matrix (opt[0][0])).transpose(); int image_lmax = 0; @@ -182,7 +183,7 @@ void run () CONSOLE ("SH series detected, performing FOD registration"); do_reorientation = true; if (!directions_cartesian.cols()) - directions_cartesian = Math::SH::spherical2cartesian (DWI::Directions::electrostatic_repulsion_60()).transpose(); + directions_cartesian = Math::Sphere::spherical2cartesian (DWI::Directions::electrostatic_repulsion_60()).transpose(); } else { do_reorientation = false; if (directions_cartesian.cols()) @@ -848,7 +849,7 @@ void run () Registration::Transform::reorient_warp ("reorienting FODs", im1_transformed, deform_field, - Math::SH::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); + Math::Sphere::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); } else if (do_affine) { Filter::reslice (im1_image, im1_transformed, affine.get_transform(), Adapter::AutoOverSample, 0.0); @@ -857,7 +858,7 @@ void run () im1_transformed, im1_transformed, affine.get_transform(), - Math::SH::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); + Math::Sphere::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); } else { // rigid Filter::reslice (im1_image, im1_transformed, rigid.get_transform(), Adapter::AutoOverSample, 0.0); if (do_reorientation) @@ -865,7 +866,7 @@ void run () im1_transformed, im1_transformed, rigid.get_transform(), - Math::SH::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); + Math::Sphere::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); } } @@ -885,7 +886,7 @@ void run () Filter::warp (im1_image, im1_midway, im1_deform_field, 0.0); if (do_reorientation) Registration::Transform::reorient_warp ("reorienting FODs", im1_midway, im1_deform_field, - Math::SH::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); + Math::Sphere::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); Image 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); @@ -893,7 +894,7 @@ void run () Filter::warp (im2_image, im2_midway, im2_deform_field, 0.0); if (do_reorientation) Registration::Transform::reorient_warp ("reorienting FODs", im2_midway, im2_deform_field, - Math::SH::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); + Math::Sphere::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); } else if (do_affine) { affine_registration.write_transformed_images (im1_image, im2_image, affine, im1_midway_transformed_path, im2_midway_transformed_path, do_reorientation); diff --git a/cmd/mrtransform.cpp b/cmd/mrtransform.cpp index ec5fe1af8a..daa3fae74f 100644 --- a/cmd/mrtransform.cpp +++ b/cmd/mrtransform.cpp @@ -19,6 +19,7 @@ #include "progressbar.h" #include "image.h" #include "math/math.h" +#include "math/sphere.h" #include "interp/nearest.h" #include "interp/linear.h" #include "interp/cubic.h" @@ -351,7 +352,7 @@ void run () directions_az_el = load_matrix (opt[0][0]); else directions_az_el = DWI::Directions::electrostatic_repulsion_300(); - Math::SH::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); diff --git a/lib/math/SH.h b/lib/math/SH.h index 0f3ab1d6de..7bdc889c65 100644 --- a/lib/math/SH.h +++ b/lib/math/SH.h @@ -347,80 +347,6 @@ namespace MR } - //! convert spherical coordinates to Cartesian coordinates - template - inline typename std::enable_if::type - spherical2cartesian (const VectorType1& az_el_r, VectorType2&& xyz) - { - if (az_el_r.size() == 3) { - xyz[0] = az_el_r[2] * std::sin (az_el_r[1]) * std::cos (az_el_r[0]); - xyz[1] = az_el_r[2] * std::sin (az_el_r[1]) * std::sin (az_el_r[0]); - xyz[2] = az_el_r[2] * cos (az_el_r[1]); - } - else { - xyz[0] = std::sin (az_el_r[1]) * std::cos (az_el_r[0]); - xyz[1] = std::sin (az_el_r[1]) * std::sin (az_el_r[0]); - xyz[2] = cos (az_el_r[1]); - } - } - - - //! convert matrix of spherical coordinates to Cartesian coordinates - template - inline typename std::enable_if::type - spherical2cartesian (const MatrixType1& az_el, MatrixType2&& cartesian) - { - cartesian.resize (az_el.rows(), 3); - for (ssize_t dir = 0; dir < az_el.rows(); ++dir) - spherical2cartesian (az_el.row (dir), cartesian.row (dir)); - } - - //! convert matrix of spherical coordinates to Cartesian coordinates - template - inline typename std::enable_if::type - spherical2cartesian (const MatrixType& az_el) - { - Eigen::MatrixXd cartesian (az_el.rows(), 3); - for (ssize_t dir = 0; dir < az_el.rows(); ++dir) - spherical2cartesian (az_el.row (dir), cartesian.row (dir)); - return cartesian; - } - - - - //! convert Cartesian coordinates to spherical coordinates - template - inline typename std::enable_if::type - cartesian2spherical (const VectorType1& xyz, VectorType2&& az_el_r) - { - auto r = std::sqrt (Math::pow2(xyz[0]) + Math::pow2(xyz[1]) + Math::pow2(xyz[2])); - az_el_r[0] = std::atan2 (xyz[1], xyz[0]); - az_el_r[1] = std::acos (xyz[2] / r); - if (az_el_r.size() == 3) - az_el_r[2] = r; - } - - //! convert matrix of Cartesian coordinates to spherical coordinates - template - inline typename std::enable_if::type - cartesian2spherical (const MatrixType1& cartesian, MatrixType2&& az_el, bool include_r = false) - { - az_el.allocate (cartesian.rows(), include_r ? 3 : 2); - for (ssize_t dir = 0; dir < cartesian.rows(); ++dir) - cartesian2spherical (cartesian.row (dir), az_el.row (dir)); - } - - //! convert matrix of Cartesian coordinates to spherical coordinates - template - inline typename std::enable_if::type - cartesian2spherical (const MatrixType& cartesian, bool include_r = false) - { - Eigen::MatrixXd az_el (cartesian.rows(), include_r ? 3 : 2); - for (ssize_t dir = 0; dir < cartesian.rows(); ++dir) - cartesian2spherical (cartesian.row (dir), az_el.row (dir)); - return az_el; - } - diff --git a/lib/math/sphere.h b/lib/math/sphere.h new file mode 100644 index 0000000000..8fe7e5c353 --- /dev/null +++ b/lib/math/sphere.h @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __math_sphere_h__ +#define __math_sphere_h__ + +#include +#include +#include + +#include + +#include "math/math.h" + +namespace MR +{ + namespace Math + { + namespace Sphere + { + + + + //! convert spherical coordinates to Cartesian coordinates + template + inline typename std::enable_if::type + spherical2cartesian (const VectorType1& az_el_r, VectorType2&& xyz) + { + if (az_el_r.size() == 3) { + xyz[0] = az_el_r[2] * std::sin (az_el_r[1]) * std::cos (az_el_r[0]); + xyz[1] = az_el_r[2] * std::sin (az_el_r[1]) * std::sin (az_el_r[0]); + xyz[2] = az_el_r[2] * cos (az_el_r[1]); + } else { + xyz[0] = std::sin (az_el_r[1]) * std::cos (az_el_r[0]); + xyz[1] = std::sin (az_el_r[1]) * std::sin (az_el_r[0]); + xyz[2] = cos (az_el_r[1]); + } + } + + + + //! convert matrix of spherical coordinates to Cartesian coordinates + template + inline typename std::enable_if::type + spherical2cartesian (const MatrixType1& az_el, MatrixType2&& cartesian) + { + cartesian.resize (az_el.rows(), 3); + for (ssize_t dir = 0; dir < az_el.rows(); ++dir) + spherical2cartesian (az_el.row (dir), cartesian.row (dir)); + } + + + + //! convert matrix of spherical coordinates to Cartesian coordinates + template + inline typename std::enable_if::type + spherical2cartesian (const MatrixType& az_el) + { + Eigen::MatrixXd cartesian (az_el.rows(), 3); + for (ssize_t dir = 0; dir < az_el.rows(); ++dir) + spherical2cartesian (az_el.row (dir), cartesian.row (dir)); + return cartesian; + } + + + + //! convert Cartesian coordinates to spherical coordinates + template + inline typename std::enable_if::type + cartesian2spherical (const VectorType1& xyz, VectorType2&& az_el_r) + { + auto r = std::sqrt (Math::pow2(xyz[0]) + Math::pow2(xyz[1]) + Math::pow2(xyz[2])); + az_el_r[0] = std::atan2 (xyz[1], xyz[0]); + az_el_r[1] = std::acos (xyz[2] / r); + if (az_el_r.size() == 3) + az_el_r[2] = r; + } + + + + //! convert matrix of Cartesian coordinates to spherical coordinates + template + inline typename std::enable_if::type + cartesian2spherical (const MatrixType1& cartesian, MatrixType2&& az_el, bool include_r = false) + { + az_el.allocate (cartesian.rows(), include_r ? 3 : 2); + for (ssize_t dir = 0; dir < cartesian.rows(); ++dir) + cartesian2spherical (cartesian.row (dir), az_el.row (dir)); + } + + + + //! convert matrix of Cartesian coordinates to spherical coordinates + template + inline typename std::enable_if::type + cartesian2spherical (const MatrixType& cartesian, bool include_r = false) + { + Eigen::MatrixXd az_el (cartesian.rows(), include_r ? 3 : 2); + for (ssize_t dir = 0; dir < cartesian.rows(); ++dir) + cartesian2spherical (cartesian.row (dir), az_el.row (dir)); + return az_el; + } + + + + } + } +} + +#endif diff --git a/src/dwi/directions/file.cpp b/src/dwi/directions/file.cpp index 8a33ad596d..cd9aa00f5e 100644 --- a/src/dwi/directions/file.cpp +++ b/src/dwi/directions/file.cpp @@ -14,6 +14,8 @@ */ #include "dwi/directions/file.h" +#include "math/math.h" + namespace MR { namespace DWI { namespace Directions { @@ -22,7 +24,7 @@ namespace MR { { auto directions = load_matrix<> (filename); if (directions.cols() == 2) - directions = Math::SH::spherical2cartesian (directions); + directions = Math::Sphere::spherical2cartesian (directions); else { if (directions.cols() != 3) throw Exception ("unexpected number of columns for directions file \"" + filename + "\""); diff --git a/src/dwi/directions/file.h b/src/dwi/directions/file.h index eb6c77b401..d68ce8556f 100644 --- a/src/dwi/directions/file.h +++ b/src/dwi/directions/file.h @@ -17,7 +17,7 @@ #ifndef __dwi_directions_load_h__ #define __dwi_directions_load_h__ -#include "math/SH.h" +#include "math/sphere.h" namespace MR { namespace DWI { @@ -29,7 +29,7 @@ namespace MR { inline void save_cartesian (const MatrixType& directions, const std::string& filename) { if (directions.cols() == 2) - save_matrix (Math::SH::spherical2cartesian (directions), filename); + save_matrix (Math::Sphere::spherical2cartesian (directions), filename); else save_matrix (directions, filename); } @@ -38,7 +38,7 @@ namespace MR { inline void save_spherical (const MatrixType& directions, const std::string& filename) { if (directions.cols() == 3) - save_matrix (Math::SH::cartesian2spherical (directions), filename); + save_matrix (Math::Sphere::cartesian2spherical (directions), filename); else save_matrix (directions, filename); } diff --git a/src/dwi/gradient.h b/src/dwi/gradient.h index 3b06b07a4d..dd16748ac8 100644 --- a/src/dwi/gradient.h +++ b/src/dwi/gradient.h @@ -27,6 +27,7 @@ #include "file/path.h" #include "file/config.h" #include "header.h" +#include "math/sphere.h" #include "math/SH.h" #include "dwi/shells.h" @@ -90,7 +91,7 @@ namespace MR if (dirs.cols() == 2) // spherical coordinates: g = dirs; else // Cartesian to spherical: - g = Math::SH::cartesian2spherical (dirs).leftCols(2); + g = Math::Sphere::cartesian2spherical (dirs).leftCols(2); auto v = Eigen::JacobiSVD (Math::SH::init_transform (g, lmax)).singularValues(); return v[0] / v[v.size()-1]; From 8166e2783036e01800b4b489ed8b62bc3f787318 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 26 Sep 2016 13:38:55 +1000 Subject: [PATCH 155/723] Make use of ZSH convenience functions throughout relevant code --- cmd/sh2response.cpp | 5 +++-- src/dwi/sdeconv/csd.h | 11 +++++------ src/dwi/sdeconv/msmt_csd.h | 5 +++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cmd/sh2response.cpp b/cmd/sh2response.cpp index b765588e21..5a5596fb93 100644 --- a/cmd/sh2response.cpp +++ b/cmd/sh2response.cpp @@ -24,6 +24,7 @@ #include "math/math.h" #include "math/SH.h" +#include "math/ZSH.h" #include "dwi/gradient.h" #include "dwi/shells.h" @@ -82,7 +83,7 @@ void run () throw Exception ("input direction image \"" + std::string (argument[2]) + "\" must contain precisely 3 volumes"); Eigen::VectorXd delta; - std::vector response (lmax/2 + 1, 0.0); + std::vector response (Math::ZSH::NforL (lmax), 0.0); size_t count = 0; auto loop = Loop ("estimating response function", SH, 0, 3); @@ -116,7 +117,7 @@ void run () d_dot_s += s*delta[i]; d_dot_d += Math::pow2 (delta[i]); } - response[l/2] += d_dot_s / d_dot_d; + response[Math::ZSH::index(l)] += d_dot_s / d_dot_d; } ++count; } diff --git a/src/dwi/sdeconv/csd.h b/src/dwi/sdeconv/csd.h index 855f58374c..90421b8a4d 100644 --- a/src/dwi/sdeconv/csd.h +++ b/src/dwi/sdeconv/csd.h @@ -20,6 +20,7 @@ #include "header.h" #include "dwi/gradient.h" #include "math/SH.h" +#include "math/ZSH.h" #include "dwi/directions/predefined.h" #include "math/least_squares.h" @@ -108,7 +109,7 @@ namespace MR void set_response (const Eigen::MatrixBase& in) { response = in; - lmax_response = 2*(response.size()-1); + lmax_response = Math::ZSH::LforN (response.size()); } @@ -125,13 +126,11 @@ namespace MR if (!init_filter.size()) init_filter = Eigen::VectorXd::Ones(3); - init_filter.conservativeResize (size_t (lmax_response/2)+1); + init_filter.conservativeResizeLike (Eigen::VectorXd::Zero (Math::ZSH::NforL (lmax_response))); auto RH = SH2RH (response); - if (RH.size() < 1+lmax/2) { - RH.conservativeResize (1+lmax/2); - RH.tail (RH.size() - response.size()).setZero(); - } + if (size_t(RH.size()) < Math::ZSH::NforL (lmax)) + RH.conservativeResizeLike (Eigen::VectorXd::Zero (Math::ZSH::NforL (lmax))); // inverse sdeconv for initialisation: auto fconv = init_transform (DW_dirs, lmax_response); diff --git a/src/dwi/sdeconv/msmt_csd.h b/src/dwi/sdeconv/msmt_csd.h index 3ef20f8f3d..f5154011e5 100644 --- a/src/dwi/sdeconv/msmt_csd.h +++ b/src/dwi/sdeconv/msmt_csd.h @@ -23,6 +23,7 @@ #include "math/constrained_least_squares.h" #include "math/math.h" #include "math/SH.h" +#include "math/ZSH.h" #include "dwi/directions/predefined.h" #include "dwi/gradient.h" @@ -103,7 +104,7 @@ namespace MR if (size_t(responses[t].rows()) != num_shells()) throw Exception ("number of rows in response function must match number of b-value shells"); // Pad response functions out to the requested lmax for this tissue - responses[t].conservativeResizeLike (Eigen::MatrixXd::Zero (num_shells(), lmax[t]/2+1)); + responses[t].conservativeResizeLike (Eigen::MatrixXd::Zero (num_shells(), Math::ZSH::NforL (lmax[t]))); } ////////////////////////////////////////////////// @@ -232,7 +233,7 @@ namespace MR // Store the lmax for each tissue based on their response functions; // if the user doesn't manually specify lmax, these will determine // the lmax of each tissue ODF output - lmax_response.push_back ((r.cols()-1)*2); + lmax_response.push_back (Math::ZSH::LforN (r.cols())); } } From bd0bc384f9201451f64cdcc8ef2a39172d672654 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 26 Sep 2016 13:43:44 +1000 Subject: [PATCH 156/723] Add documentation for amp2response --- docs/reference/commands/amp2response.rst | 67 ++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 docs/reference/commands/amp2response.rst diff --git a/docs/reference/commands/amp2response.rst b/docs/reference/commands/amp2response.rst new file mode 100644 index 0000000000..0eed4227ac --- /dev/null +++ b/docs/reference/commands/amp2response.rst @@ -0,0 +1,67 @@ +.. _amp2response: + +amp2response +=========== + +Synopsis +-------- + +:: + + amp2response [ options ] amps mask directions response + +- *amps*: the amplitudes image +- *mask*: the mask containing the voxels from which to estimate the response function +- *directions*: a 4D image containing the estimated fibre directions +- *response*: the output zonal spherical harmonic coefficients + +Description +----------- + +test suite for new mechanisms for estimating spherical deconvolution response functions + +Options +------- + +- **-directions path** provide an external text file containing the directions along which the amplitudes are sampled + +DW Shell selection options +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-shell list** specify one or more diffusion-weighted gradient shells to use during processing, as a comma-separated list of the desired approximate b-values. Note that some commands are incompatible with multiple shells, and will throw an error if more than one b-value is provided. + +- **-lmax value** specify the maximum harmonic degree of the response function to estimate + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) + +- **-failonwarn** terminate program if a warning is produced + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +-------------- + + + +**Author:** Robert E. Smith (robert.smith@florey.edu.au + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + From 7b1bb892857bacfa9e2a2dddeb5587d09ac6883f Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 26 Sep 2016 14:13:38 +1000 Subject: [PATCH 157/723] amp2response: FIx license --- cmd/amp2response.cpp | 33 ++++++++++---------------- docs/reference/commands/dwidenoise.rst | 4 +++- docs/reference/commands_list.rst | 2 ++ 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/cmd/amp2response.cpp b/cmd/amp2response.cpp index 3e108157d2..451fbfcd42 100644 --- a/cmd/amp2response.cpp +++ b/cmd/amp2response.cpp @@ -1,24 +1,17 @@ /* - Copyright 2008 Brain Research Institute, Melbourne, Australia - - Written by J-Donald Tournier 2014 - - This file is part of MRtrix. - - MRtrix is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - MRtrix is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with MRtrix. If not, see . - -*/ + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ #include diff --git a/docs/reference/commands/dwidenoise.rst b/docs/reference/commands/dwidenoise.rst index fef943fb34..ecc9e671b9 100644 --- a/docs/reference/commands/dwidenoise.rst +++ b/docs/reference/commands/dwidenoise.rst @@ -55,7 +55,9 @@ Standard options References ^^^^^^^^^^ -Veraart, J.; Fieremans, E. & Novikov, D.S. Diffusion MRI noise mapping using random matrix theory Magn. Res. Med., 2016, early view, doi: 10.1002/mrm.26059 +Veraart, J.; Novikov, D.S.; Christiaens, D.; Ades-aron, B.; Sijbers, J. & Fieremans, E. Denoising of diffusion MRI using random matrix theory. NeuroImage, 2016, in press, doi: 10.1016/j.neuroimage.2016.08.016 + +Veraart, J.; Fieremans, E. & Novikov, D.S. Diffusion MRI noise mapping using random matrix theory. Magn. Res. Med., 2016, early view, doi: 10.1002/mrm.26059 -------------- diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index 50bb905bf1..a321742107 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -17,6 +17,8 @@ List of MRtrix3 commands commands/afdconnectivity + commands/amp2response + commands/amp2sh commands/connectome2tck From c515fc049479a4d063156b2be3db9ed5af091809 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 26 Sep 2016 14:29:52 +1000 Subject: [PATCH 158/723] amp2response: Fix command description --- cmd/amp2response.cpp | 8 ++++++-- docs/reference/commands/amp2response.rst | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cmd/amp2response.cpp b/cmd/amp2response.cpp index 451fbfcd42..6fdbbdf294 100644 --- a/cmd/amp2response.cpp +++ b/cmd/amp2response.cpp @@ -45,10 +45,14 @@ using namespace App; void usage () { - AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au"; + AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; DESCRIPTION - + "test suite for new mechanisms for estimating spherical deconvolution response functions"; + + "Estimate response function coefficients based on the DWI signal in single-fibre voxels. " + "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)."; ARGUMENTS + Argument ("amps", "the amplitudes image").type_image_in() diff --git a/docs/reference/commands/amp2response.rst b/docs/reference/commands/amp2response.rst index 0eed4227ac..7632c2846f 100644 --- a/docs/reference/commands/amp2response.rst +++ b/docs/reference/commands/amp2response.rst @@ -18,7 +18,7 @@ Synopsis Description ----------- -test suite for new mechanisms for estimating spherical deconvolution response functions +Estimate response function coefficients based on the DWI signal in single-fibre voxels. 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). Options ------- @@ -55,7 +55,7 @@ Standard options -**Author:** Robert E. Smith (robert.smith@florey.edu.au +**Author:** Robert E. Smith (robert.smith@florey.edu.au) **Copyright:** Copyright (c) 2008-2016 the MRtrix3 contributors From 981afc8f9319ee4d969a222f40609661ad6e0d7b Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 26 Sep 2016 16:26:01 +1000 Subject: [PATCH 159/723] NIfTI: Fix opening images < 3D Was causing read/write outside allocated memory. --- lib/file/nifti1_utils.cpp | 22 ++++++++-------------- lib/file/nifti2_utils.cpp | 22 ++++++++-------------- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/lib/file/nifti1_utils.cpp b/lib/file/nifti1_utils.cpp index 4fd66aae04..0aa2159efe 100644 --- a/lib/file/nifti1_utils.cpp +++ b/lib/file/nifti1_utils.cpp @@ -181,22 +181,16 @@ namespace MR M(2,3) = Raw::fetch_ (&NH.srow_z[3], is_BE); // get voxel sizes: - H.spacing(0) = std::sqrt (Math::pow2 (M(0,0)) + Math::pow2 (M(1,0)) + Math::pow2 (M(2,0))); - H.spacing(1) = std::sqrt (Math::pow2 (M(0,1)) + Math::pow2 (M(1,1)) + Math::pow2 (M(2,1))); - H.spacing(2) = std::sqrt (Math::pow2 (M(0,2)) + Math::pow2 (M(1,2)) + Math::pow2 (M(2,2))); + for (size_t axis = 0; axis != 3; ++axis) { + if (ndim > axis) + H.spacing(axis) = std::sqrt (Math::pow2 (M(0,axis)) + Math::pow2 (M(1,axis)) + Math::pow2 (M(2,axis))); + } // normalize each transform axis: - M (0,0) /= H.spacing (0); - M (1,0) /= H.spacing (0); - M (2,0) /= H.spacing (0); - - M (0,1) /= H.spacing (1); - M (1,1) /= H.spacing (1); - M (2,1) /= H.spacing (1); - - M (0,2) /= H.spacing (2); - M (1,2) /= H.spacing (2); - M (2,2) /= H.spacing (2); + for (size_t axis = 0; axis != 3; ++axis) { + if (ndim > axis) + M.col(axis).array() /= H.spacing (axis); + } } else if (Raw::fetch_ (&NH.qform_code, is_BE)) { diff --git a/lib/file/nifti2_utils.cpp b/lib/file/nifti2_utils.cpp index 836a84a162..2777ea559b 100644 --- a/lib/file/nifti2_utils.cpp +++ b/lib/file/nifti2_utils.cpp @@ -175,22 +175,16 @@ namespace MR M(2,3) = Raw::fetch_ (&NH.srow_z[3], is_BE); // get voxel sizes: - H.spacing(0) = std::sqrt (Math::pow2 (M(0,0)) + Math::pow2 (M(1,0)) + Math::pow2 (M(2,0))); - H.spacing(1) = std::sqrt (Math::pow2 (M(0,1)) + Math::pow2 (M(1,1)) + Math::pow2 (M(2,1))); - H.spacing(2) = std::sqrt (Math::pow2 (M(0,2)) + Math::pow2 (M(1,2)) + Math::pow2 (M(2,2))); + for (size_t axis = 0; axis != 3; ++axis) { + if (ndim > axis) + H.spacing(axis) = std::sqrt (Math::pow2 (M(0,axis)) + Math::pow2 (M(1,axis)) + Math::pow2 (M(2,axis))); + } // normalize each transform axis: - M (0,0) /= H.spacing (0); - M (1,0) /= H.spacing (0); - M (2,0) /= H.spacing (0); - - M (0,1) /= H.spacing (1); - M (1,1) /= H.spacing (1); - M (2,1) /= H.spacing (1); - - M (0,2) /= H.spacing (2); - M (1,2) /= H.spacing (2); - M (2,2) /= H.spacing (2); + for (size_t axis = 0; axis != 3; ++axis) { + if (ndim > axis) + M.col(axis).array() /= H.spacing (axis); + } } else if (Raw::fetch_ (&NH.qform_code, is_BE)) { { // TODO update with Eigen3 Quaternions From 05dee093c342c55792a1d407471333f23c847842 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 26 Sep 2016 16:47:32 +1000 Subject: [PATCH 160/723] Fix compilation of src/surface/polygon.h --- lib/file/nifti1_utils.cpp | 4 ++-- lib/file/nifti2_utils.cpp | 4 ++-- src/surface/polygon.h | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/file/nifti1_utils.cpp b/lib/file/nifti1_utils.cpp index 0aa2159efe..25e8bd0a51 100644 --- a/lib/file/nifti1_utils.cpp +++ b/lib/file/nifti1_utils.cpp @@ -182,13 +182,13 @@ namespace MR // get voxel sizes: for (size_t axis = 0; axis != 3; ++axis) { - if (ndim > axis) + if (size_t(ndim) > axis) H.spacing(axis) = std::sqrt (Math::pow2 (M(0,axis)) + Math::pow2 (M(1,axis)) + Math::pow2 (M(2,axis))); } // normalize each transform axis: for (size_t axis = 0; axis != 3; ++axis) { - if (ndim > axis) + if (size_t(ndim) > axis) M.col(axis).array() /= H.spacing (axis); } diff --git a/lib/file/nifti2_utils.cpp b/lib/file/nifti2_utils.cpp index 2777ea559b..0147430b74 100644 --- a/lib/file/nifti2_utils.cpp +++ b/lib/file/nifti2_utils.cpp @@ -176,13 +176,13 @@ namespace MR // get voxel sizes: for (size_t axis = 0; axis != 3; ++axis) { - if (ndim > axis) + if (size_t(ndim) > axis) H.spacing(axis) = std::sqrt (Math::pow2 (M(0,axis)) + Math::pow2 (M(1,axis)) + Math::pow2 (M(2,axis))); } // normalize each transform axis: for (size_t axis = 0; axis != 3; ++axis) { - if (ndim > axis) + if (size_t(ndim) > axis) M.col(axis).array() /= H.spacing (axis); } diff --git a/src/surface/polygon.h b/src/surface/polygon.h index 82ba463a03..8945b6044c 100644 --- a/src/surface/polygon.h +++ b/src/surface/polygon.h @@ -18,6 +18,7 @@ #include #include +#include #include namespace MR From d11d51458da29c3e38fa0965a2e3064e1dd04923 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 27 Sep 2016 11:32:10 +1000 Subject: [PATCH 161/723] Further incorporation of ZSH Function converting 'SH' to rotational harmonics (RH) was actually operating on ZSH coefficients, and so has been explicitly moved. Generation of response function coefficients based on a tensor model with a given FA was also moved for the same reason. --- cmd/shconv.cpp | 5 ++- lib/math/SH.h | 46 ---------------------- lib/math/ZSH.h | 2 +- src/dwi/sdeconv/csd.h | 2 +- src/dwi/tractography/GT/externalenergy.cpp | 11 +++--- 5 files changed, 11 insertions(+), 55 deletions(-) diff --git a/cmd/shconv.cpp b/cmd/shconv.cpp index e92a9b55ed..3b2802aa8f 100644 --- a/cmd/shconv.cpp +++ b/cmd/shconv.cpp @@ -20,6 +20,7 @@ #include "algo/threaded_loop.h" #include "image.h" #include "math/SH.h" +#include "math/ZSH.h" using namespace MR; using namespace App; @@ -81,9 +82,9 @@ void run() { auto image_in = Image::open (argument[0]).with_direct_io (3); Math::SH::check (image_in); - auto responseSH = load_vector(argument[1]); + auto responseZSH = load_vector(argument[1]); Eigen::Matrix responseRH; - Math::SH::SH2RH (responseRH, responseSH); + Math::ZSH::ZSH2RH (responseRH, responseZSH); auto mask = Image(); auto opt = get_options ("mask"); diff --git a/lib/math/SH.h b/lib/math/SH.h index 7bdc889c65..8394404128 100644 --- a/lib/math/SH.h +++ b/lib/math/SH.h @@ -286,28 +286,6 @@ namespace MR - template - inline VectorType1& SH2RH (VectorType1& RH, const VectorType2& sh) - { - typedef typename VectorType2::Scalar value_type; - RH.resize (sh.size()); - int lmax = 2*sh.size() +1; - Eigen::Matrix AL (lmax+1); - Legendre::Plm_sph (AL, lmax, 0, 1.0); - for (ssize_t l = 0; l < sh.size(); l++) - RH[l] = sh[l]/ AL[2*l]; - return RH; - } - - template - inline Eigen::Matrix SH2RH (const VectorType& sh) - { - Eigen::Matrix RH (sh.size()); - SH2RH (RH, sh); - return RH; - } - - //! perform spherical convolution, in place /*! perform spherical convolution of SH coefficients \a sh with response @@ -350,30 +328,6 @@ namespace MR - //! compute axially-symmetric SH coefficients corresponding to specified tensor - template - inline VectorType& FA2SH ( - VectorType& sh, default_type FA, default_type ADC, default_type bvalue, int lmax, int precision = 100) - { - default_type a = FA/sqrt (3.0 - 2.0*FA*FA); - default_type ev1 = ADC* (1.0+2.0*a), ev2 = ADC* (1.0-a); - - Eigen::VectorXd sigs (precision); - Eigen::MatrixXd SHT (precision, lmax/2+1); - Eigen::Matrix AL; - - for (int i = 0; i < precision; i++) { - default_type el = i*Math::pi / (2.0* (precision-1)); - sigs[i] = exp (-bvalue* (ev1*std::cos (el) *std::cos (el) + ev2*std::sin (el) *std::sin (el))); - Legendre::Plm_sph (AL, lmax, 0, std::cos (el)); - for (int l = 0; l < lmax/2+1; l++) SHT (i,l) = AL[2*l]; - } - - return (sh = pinv(SHT) * sigs); - } - - - //! used to speed up SH calculation template class PrecomputedFraction diff --git a/lib/math/ZSH.h b/lib/math/ZSH.h index a6810741c4..82c11824c3 100644 --- a/lib/math/ZSH.h +++ b/lib/math/ZSH.h @@ -231,7 +231,7 @@ namespace MR const size_t lmax = LforN (zsh.size()); Eigen::Matrix AL (lmax+1); Legendre::Plm_sph (AL, lmax, 0, 1.0); - for (size_t l = 0; l <= lmax; ++l) + for (size_t l = 0; l <= lmax; l += 2) rh[index(l)] = zsh[index(l)] / AL[l]; return rh; } diff --git a/src/dwi/sdeconv/csd.h b/src/dwi/sdeconv/csd.h index 90421b8a4d..7de395ee27 100644 --- a/src/dwi/sdeconv/csd.h +++ b/src/dwi/sdeconv/csd.h @@ -128,7 +128,7 @@ namespace MR init_filter = Eigen::VectorXd::Ones(3); init_filter.conservativeResizeLike (Eigen::VectorXd::Zero (Math::ZSH::NforL (lmax_response))); - auto RH = SH2RH (response); + auto RH = Math::ZSH::ZSH2RH (response); if (size_t(RH.size()) < Math::ZSH::NforL (lmax)) RH.conservativeResizeLike (Eigen::VectorXd::Zero (Math::ZSH::NforL (lmax))); diff --git a/src/dwi/tractography/GT/externalenergy.cpp b/src/dwi/tractography/GT/externalenergy.cpp index 8efcf1ff50..e8e556eb54 100644 --- a/src/dwi/tractography/GT/externalenergy.cpp +++ b/src/dwi/tractography/GT/externalenergy.cpp @@ -18,6 +18,7 @@ #include "dwi/gradient.h" #include "dwi/shells.h" #include "math/SH.h" +#include "math/ZSH.h" #include "algo/loop.h" @@ -69,16 +70,16 @@ namespace MR { Ak.setZero(); Eigen::VectorXd delta_vec (ncols); - Eigen::VectorXd wmr_sh (lmax/2+1), wmr_rh (lmax/2+1); - wmr_sh.setZero(); + Eigen::VectorXd wmr_zsh (Math::ZSH::NforL (lmax)), wmr_rh (Math::ZSH::NforL (lmax)); + wmr_zsh.setZero(); Eigen::Vector3d unit_dir; double wmr0; for (size_t s = 0; s < shells.count(); s++) { - for (int l = 0; l <= lmax/2; l++) - wmr_sh[l] = (l < props.resp_WM.cols()) ? props.resp_WM(s, l) : 0.0; - wmr_rh = Math::SH::SH2RH(wmr_sh); + for (int i = 0; i < int(Math::ZSH::NforL (lmax)); ++i) + wmr_zsh[i] = (i < props.resp_WM.cols()) ? props.resp_WM(s, i) : 0.0; + wmr_rh = Math::ZSH::ZSH2RH (wmr_zsh); wmr0 = props.resp_WM(s,0) / std::sqrt(M_4PI); for (size_t r : shells[s].get_volumes()) From 62e70c441b6ac544165659a711e4daefab7fdce9 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 27 Sep 2016 14:02:51 +1000 Subject: [PATCH 162/723] amp2response: Handle multi-shell data --- cmd/amp2response.cpp | 313 +++++++++++++---------- docs/reference/commands/amp2response.rst | 2 +- src/dwi/shells.cpp | 9 +- 3 files changed, 191 insertions(+), 133 deletions(-) diff --git a/cmd/amp2response.cpp b/cmd/amp2response.cpp index 6fdbbdf294..5ea6aae63a 100644 --- a/cmd/amp2response.cpp +++ b/cmd/amp2response.cpp @@ -37,8 +37,8 @@ using namespace App; -#define AMP2RESPONSE_DEBUG -#define AMP2RESPONSE_PERVOXEL_IMAGES +//#define AMP2RESPONSE_DEBUG +//#define AMP2RESPONSE_PERVOXEL_IMAGES @@ -49,10 +49,14 @@ void usage () DESCRIPTION + "Estimate response function coefficients based on the DWI signal in single-fibre voxels. " + "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)."; + "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."; ARGUMENTS + Argument ("amps", "the amplitudes image").type_image_in() @@ -66,8 +70,9 @@ void usage () + DWI::ShellOption - + Option ("lmax", "specify the maximum harmonic degree of the response function to estimate") - + Argument ("value").type_integer (0, 20); + + 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(); } @@ -108,13 +113,15 @@ void run () // Get directions from either selecting a b-value shell, or the header, or external file auto header = Header::open (argument[0]); - Eigen::MatrixXd dirs_azel; - std::vector volumes; + // May be dealing with multiple shells + std::vector dirs_azel; + std::vector> volumes; + std::unique_ptr shells; auto opt = get_options ("directions"); if (opt.size()) { - dirs_azel = load_matrix (opt[0][0]); - volumes = all_volumes (dirs_azel.rows()); + dirs_azel.push_back (std::move (load_matrix (opt[0][0]))); + volumes.push_back (std::move (all_volumes (dirs_azel.size()))); } else { auto hit = header.keyval().find ("directions"); if (hit != header.keyval().end()) { @@ -123,28 +130,52 @@ void run () auto v = parse_floats (line); dir_vector.insert (dir_vector.end(), v.begin(), v.end()); } - dirs_azel.resize (dir_vector.size() / 2, 2); + Eigen::MatrixXd directions (dir_vector.size() / 2, 2); for (size_t i = 0; i < dir_vector.size(); i += 2) { - dirs_azel (i/2, 0) = dir_vector[i]; - dirs_azel (i/2, 1) = dir_vector[i+1]; + directions (i/2, 0) = dir_vector[i]; + directions (i/2, 1) = dir_vector[i+1]; } - volumes = all_volumes (dirs_azel.rows()); + dirs_azel.push_back (std::move (directions)); + volumes.push_back (std::move (all_volumes (dirs_azel.size()))); } else { auto grad = DWI::get_valid_DW_scheme (header); - DWI::Shells shells (grad); - shells.select_shells (false, true); - volumes = shells.largest().get_volumes(); - dirs_azel = DWI::gen_direction_matrix (grad, volumes); + shells.reset (new DWI::Shells (grad)); + shells->select_shells (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())); + } } } - Eigen::MatrixXd dirs_cartesian = Math::Sphere::spherical2cartesian (dirs_azel); - - // Because the amp->SH transform doesn't need to be applied per voxel, - // lmax is effectively unconstrained. Therefore generate response at - // lmax=8 regardless of number of input volumes, unless user - // explicitly requests something else - const size_t lmax = get_option_value ("lmax", 8); + std::vector lmax; + int max_lmax = 0; + opt = get_options ("lmax"); + if (opt.size()) { + 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()) + ")"); + for (auto i : lmax) { + if (i < 0) + throw Exception ("Values specified for lmax must be non-negative"); + if (i%2) + throw Exception ("Values specified for lmax must be even"); + max_lmax = std::max (max_lmax, i); + } + } else { + // Auto-fill lmax + // Because the amp->SH transform doesn't need to be applied per voxel, + // lmax is effectively unconstrained. Therefore generate response at + // lmax=8 regardless of number of input volumes. + // - 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); + else + lmax.push_back (8); + } + max_lmax = (shells && shells->smallest().is_bzero() && lmax.size() == 1) ? 0 : 8; + } auto image = header.get_image(); auto mask = Image::open (argument[1]); @@ -154,138 +185,160 @@ void run () throw Exception ("input direction image \"" + std::string (argument[2]) + "\" does not have expected dimensions"); check_dimensions (image, dir_image, 0, 3); - // All directions from all SF voxels get concatenated into a single large matrix - Eigen::MatrixXd cat_transforms; - Eigen::VectorXd cat_data; + Eigen::MatrixXd responses (dirs_azel.size(), Math::ZSH::NforL (max_lmax)); + + for (size_t shell_index = 0; shell_index != dirs_azel.size(); ++shell_index) { + + std::string shell_desc = (dirs_azel.size() > 1) ? ("_shell" + str(shell_index)) : ""; + + Eigen::MatrixXd dirs_cartesian = Math::Sphere::spherical2cartesian (dirs_azel[shell_index]); + + // All directions from all SF voxels get concatenated into a single large matrix + Eigen::MatrixXd cat_transforms; + Eigen::VectorXd cat_data; #ifdef AMP2RESPONSE_DEBUG - // To make sure we've got our data rotated correctly, let's generate a scatterplot of - // elevation vs. amplitude - Eigen::MatrixXd scatter; + // To make sure we've got our data rotated correctly, let's generate a scatterplot of + // elevation vs. amplitude + Eigen::MatrixXd scatter; #endif - size_t sf_counter = 0; - for (auto l = Loop (mask) (image, mask, dir_image); l; ++l) { - if (mask.value()) { + size_t sf_counter = 0; + for (auto l = Loop (mask) (image, mask, dir_image); l; ++l) { + if (mask.value()) { - // Grab the image data - Eigen::VectorXd data (dirs_azel.rows()); - for (size_t i = 0; i != volumes.size(); ++i) { - image.index(3) = volumes[i]; - data[i] = image.value(); - } + // Grab the image data + Eigen::VectorXd data (dirs_azel[shell_index].rows()); + for (size_t i = 0; i != volumes[shell_index].size(); ++i) { + image.index(3) = volumes[shell_index][i]; + data[i] = image.value(); + } - // Grab the fibre direction - // Eventually, it might be possible to optimise these fibre directions - // during the response function fit; i.e. optimise (az,el) in each voxel - // to minimise SSE compared to the current RF estimate - Eigen::Vector3 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 - Eigen::Matrix R = gen_rotation_matrix (fibre_dir); - Eigen::Matrix rotated_dirs_cartesian (dirs_cartesian.rows(), 3); - Eigen::Vector3 vec (3), rot (3); - for (ssize_t row = 0; row != dirs_azel.rows(); ++row) { - vec = dirs_cartesian.row (row); - rot = R * vec; - rotated_dirs_cartesian.row (row) = rot; - } + // Grab the fibre direction + Eigen::Vector3 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 + Eigen::Matrix R = gen_rotation_matrix (fibre_dir); + Eigen::Matrix rotated_dirs_cartesian (dirs_cartesian.rows(), 3); + Eigen::Vector3 vec (3), rot (3); + for (ssize_t row = 0; row != dirs_azel[shell_index].rows(); ++row) { + vec = dirs_cartesian.row (row); + rot = R * vec; + rotated_dirs_cartesian.row (row) = rot; + } - // 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); + // 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); + } } - } #ifdef AMP2RESPONSE_PERVOXEL_IMAGES - // For the sake of generating a figure, output the original and rotated signals to a dixel ODF image - Header rotated_header (header); - rotated_header.size(0) = rotated_header.size(1) = rotated_header.size(2) = 1; - rotated_header.size(3) = volumes.size(); - Header nonrotated_header (rotated_header); - nonrotated_header.size(3) = header.size(3); - Eigen::MatrixXd rotated_grad (volumes.size(), 4); - for (size_t i = 0; i != volumes.size(); ++i) { - rotated_grad.block<1,3>(i, 0) = rotated_dirs_cartesian.row(i); - rotated_grad(i, 3) = 1000.0; - } - DWI::set_DW_scheme (rotated_header, rotated_grad); - Image out_rotated = Image::create ("rotated_amps_" + str(sf_counter) + ".mif", rotated_header); - Image out_nonrotated = Image::create ("nonrotated_amps_" + str(sf_counter) + ".mif", nonrotated_header); - out_rotated.index(0) = out_rotated.index(1) = out_rotated.index(2) = 0; - out_nonrotated.index(0) = out_nonrotated.index(1) = out_nonrotated.index(2) = 0; - for (size_t i = 0; i != volumes.size(); ++i) { - image.index(3) = volumes[i]; - out_rotated.index(3) = i; - out_rotated.value() = image.value(); - } - for (ssize_t i = 0; i != header.size(3); ++i) { - image.index(3) = out_nonrotated.index(3) = i; - out_nonrotated.value() = image.value(); - } + // For the sake of generating a figure, output the original and rotated signals to a dixel ODF image + Header rotated_header (header); + rotated_header.size(0) = rotated_header.size(1) = rotated_header.size(2) = 1; + rotated_header.size(3) = volumes[shell_index].size(); + Header nonrotated_header (rotated_header); + nonrotated_header.size(3) = header.size(3); + Eigen::MatrixXd rotated_grad (volumes[shell_index].size(), 4); + for (size_t i = 0; i != volumes.size(); ++i) { + rotated_grad.block<1,3>(i, 0) = rotated_dirs_cartesian.row(i); + rotated_grad(i, 3) = 1000.0; + } + DWI::set_DW_scheme (rotated_header, rotated_grad); + Image out_rotated = Image::create ("rotated_amps_" + str(sf_counter) + shell_desc + ".mif", rotated_header); + Image out_nonrotated = Image::create ("nonrotated_amps_" + str(sf_counter) + shell_desc + ".mif", nonrotated_header); + out_rotated.index(0) = out_rotated.index(1) = out_rotated.index(2) = 0; + out_nonrotated.index(0) = out_nonrotated.index(1) = out_nonrotated.index(2) = 0; + for (size_t i = 0; i != volumes[shell_index].size(); ++i) { + image.index(3) = volumes[shell_index][i]; + out_rotated.index(3) = i; + out_rotated.value() = image.value(); + } + for (ssize_t i = 0; i != header.size(3); ++i) { + image.index(3) = out_nonrotated.index(3) = i; + out_nonrotated.value() = image.value(); + } #endif - // Generate the ZSH -> amplitude transform - Eigen::MatrixXd transform = Math::ZSH::init_amp_transform (rotated_dirs_azel.col(1), lmax); + // Generate the ZSH -> amplitude transform + Eigen::MatrixXd transform = Math::ZSH::init_amp_transform (rotated_dirs_azel.col(1), lmax[shell_index]); - // Concatenate these data to the ICLS matrices - const size_t old_rows = cat_transforms.rows(); - cat_transforms.conservativeResize (old_rows + transform.rows(), transform.cols()); - cat_transforms.block (old_rows, 0, transform.rows(), transform.cols()) = transform; - cat_data.conservativeResize (old_rows + data.size()); - cat_data.tail (data.size()) = data; + // Concatenate these data to the ICLS matrices + const size_t old_rows = cat_transforms.rows(); + cat_transforms.conservativeResize (old_rows + transform.rows(), transform.cols()); + cat_transforms.block (old_rows, 0, transform.rows(), transform.cols()) = transform; + cat_data.conservativeResize (old_rows + data.size()); + cat_data.tail (data.size()) = data; #ifdef AMP2RESPONSE_DEBUG - scatter.conservativeResize (cat_data.size(), 2); - scatter.block (old_rows, 0, data.size(), 1) = rotated_dirs_azel.col(1); - scatter.block (old_rows, 1, data.size(), 1) = data; + scatter.conservativeResize (cat_data.size(), 2); + scatter.block (old_rows, 0, data.size(), 1) = rotated_dirs_azel.col(1); + scatter.block (old_rows, 1, data.size(), 1) = data; #endif - ++sf_counter; + ++sf_counter; + } } - } #ifdef AMP2RESPONSE_DEBUG - save_matrix (scatter, "scatter.csv"); + save_matrix (scatter, "scatter" + shell_desc + ".csv"); #endif - // Generate the constraint matrix - // 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; - els.resize (num_angles_constraint+1); - for (size_t i = 0; i <= num_angles_constraint; ++i) - els[i] = default_type(i) * Math::pi / 180.0; - Eigen::MatrixXd amp_transform = Math::ZSH::init_amp_transform (els, lmax); - Eigen::MatrixXd deriv_transform = Math::ZSH::init_deriv_transform (els, lmax); + Eigen::VectorXd rf; + shell_desc = (shells && shells->count() > 1) ? ("Shell b=" + str(int(std::round((*shells)[shell_index].get_mean()))) + ": ") : ""; + if (lmax[shell_index]) { + + // Generate the constraint matrix + // 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; + els.resize (num_angles_constraint+1); + for (size_t i = 0; i <= num_angles_constraint; ++i) + els[i] = default_type(i) * Math::pi / 180.0; + Eigen::MatrixXd amp_transform = Math::ZSH::init_amp_transform (els, lmax[shell_index]); + Eigen::MatrixXd deriv_transform = Math::ZSH::init_deriv_transform (els, lmax[shell_index]); - Eigen::MatrixXd constraints; - constraints.resize (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; + constraints.resize (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 (cat_transforms, constraints, 1e-10, 1e-10); - auto solver = Math::ICLS::Solver (problem); + // Initialise the problem solver + auto problem = Math::ICLS::Problem (cat_transforms, constraints, 1e-10, 1e-10); + auto solver = Math::ICLS::Solver (problem); - // Estimate the solution - Eigen::VectorXd rf; - const size_t niter = solver (rf, cat_data); + // Estimate the solution + const size_t niter = solver (rf, cat_data); - INFO ("Response function [" + str(rf.transpose()) + " ] solved after " + str(niter) + " iterations from " + str(sf_counter) + " voxels"); + INFO (shell_desc + "Response function [" + str(rf.transpose().cast()) + " ] solved after " + str(niter) + " iterations from " + str(sf_counter) + " voxels"); + + } else { + + // lmax is zero - perform a straight average of the image data + rf.resize(1); + rf[0] = cat_data.mean() * std::sqrt(4*Math::pi); + + INFO (shell_desc + "Response function [ " + str(float(rf[0])) + " ] from average of " + str(sf_counter) + " voxels"); + + } + + rf.conservativeResizeLike (Eigen::VectorXd::Zero (Math::ZSH::NforL (max_lmax))); + responses.row(shell_index) = rf; + } - save_vector (rf, argument[3]); + save_matrix (responses, argument[3]); } diff --git a/docs/reference/commands/amp2response.rst b/docs/reference/commands/amp2response.rst index 7632c2846f..935786345e 100644 --- a/docs/reference/commands/amp2response.rst +++ b/docs/reference/commands/amp2response.rst @@ -30,7 +30,7 @@ DW Shell selection options - **-shell list** specify one or more diffusion-weighted gradient shells to use during processing, as a comma-separated list of the desired approximate b-values. Note that some commands are incompatible with multiple shells, and will throw an error if more than one b-value is provided. -- **-lmax value** specify the maximum harmonic degree of the response function to estimate +- **-lmax values** specify the maximum harmonic degree of the response function to estimate (can be a comma-separated list for multi-shell data) Standard options ^^^^^^^^^^^^^^^^ diff --git a/src/dwi/shells.cpp b/src/dwi/shells.cpp index 02457919a6..e5212e2057 100644 --- a/src/dwi/shells.cpp +++ b/src/dwi/shells.cpp @@ -180,9 +180,14 @@ namespace MR } else { - if (force_single_shell && !is_single_shell()) + if (force_single_shell && !is_single_shell()) { WARN ("Multiple non-zero b-value shells detected; automatically selecting b=" + str(largest().get_mean()) + " with " + str(largest().count()) + " volumes"); - to_retain[shells.size()-1] = true; + to_retain[shells.size()-1] = true; + } else { + // Not restricted to single-shell processing, no -shells option: + // keep all shells + to_retain.clear (true); + } } From 18f4ebfc752ac13a9f028980cf4bfc147fda5cfd Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 28 Sep 2016 17:39:40 +1000 Subject: [PATCH 163/723] meshconvert: Fix and fix usage in 5ttgen fsl --- cmd/meshconvert.cpp | 4 ++-- scripts/src/_5ttgen/fsl.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/meshconvert.cpp b/cmd/meshconvert.cpp index 57c2390b35..eb8c2e2eeb 100644 --- a/cmd/meshconvert.cpp +++ b/cmd/meshconvert.cpp @@ -75,9 +75,9 @@ void run () auto opt = get_options ("transform"); if (opt.size()) { - auto H = Header::open (opt[0][2]); + auto H = Header::open (opt[0][1]); std::unique_ptr transform (new Surface::Filter::VertexTransform (H)); - switch (int(opt[0][1])) { + switch (int(opt[0][0])) { case 0: transform->set_first2real(); break; case 1: transform->set_real2first(); break; case 2: transform->set_voxel2real(); break; diff --git a/scripts/src/_5ttgen/fsl.py b/scripts/src/_5ttgen/fsl.py index 9dfcce2bb2..158975b02e 100644 --- a/scripts/src/_5ttgen/fsl.py +++ b/scripts/src/_5ttgen/fsl.py @@ -189,7 +189,7 @@ def execute(): vtk_temp_path = struct + '.vtk' if not os.path.exists(vtk_in_path): errorMessage('Missing .vtk file for structure ' + struct + '; run_first_all must have failed') - runCommand('meshconvert ' + vtk_in_path + ' ' + vtk_temp_path + ' -transform_first2real T1.nii') + runCommand('meshconvert ' + vtk_in_path + ' ' + vtk_temp_path + ' -transform first2real T1.nii') runCommand('mesh2pve ' + vtk_temp_path + ' ' + fast_t1_input + ' ' + pve_image_path) pve_image_list.append(pve_image_path) pve_cat = ' '.join(pve_image_list) From 758a23ce001deb8630fecb18b39a3f1bf552f81f Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 28 Sep 2016 17:43:04 +1000 Subject: [PATCH 164/723] 5ttgen fsl: Better masking of non-connected WM voxels In the previous implementation, a largest connected-component analysis was run on the WM partial volume image, to remove non-brain components within the brain mask labelled as WM. Now once these voxels are identified, all tissues are zeroed in those voxels - this is to ensure conformity with the 5TT format. --- scripts/src/_5ttgen/fsl.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/src/_5ttgen/fsl.py b/scripts/src/_5ttgen/fsl.py index 158975b02e..cda6d41ed6 100644 --- a/scripts/src/_5ttgen/fsl.py +++ b/scripts/src/_5ttgen/fsl.py @@ -204,14 +204,14 @@ def execute(): # Combine the tissue images into the 5TT format within the script itself # Step 1: Run LCC on the WM image - runCommand('mrthreshold ' + fast_output_prefix + '_pve_2' + fast_suffix + ' - -abs 0.001 | maskfilter - connect wm_mask.mif -largest') + runCommand('mrthreshold ' + fast_output_prefix + '_pve_2' + fast_suffix + ' - -abs 0.001 | maskfilter - connect - -connectivity | mrcalc 1 - 1 -gt -sub remove_unconnected_wm_mask.mif -datatype bit') # Step 2: Generate the images in the same fashion as the 5ttgen command - runCommand('mrconvert ' + fast_output_prefix + '_pve_0' + fast_suffix + ' csf.mif') + runCommand('mrcalc ' + fast_output_prefix + '_pve_0' + fast_suffix + ' remove_unconnected_wm_mask.mif -mult csf.mif') runCommand('mrcalc 1.0 csf.mif -sub all_sgms.mif -min sgm.mif') runCommand('mrcalc 1.0 csf.mif sgm.mif -add -sub ' + fast_output_prefix + '_pve_1' + fast_suffix + ' ' + fast_output_prefix + '_pve_2' + fast_suffix + ' -add -div multiplier.mif') runCommand('mrcalc multiplier.mif -finite multiplier.mif 0.0 -if multiplier_noNAN.mif') - runCommand('mrcalc ' + fast_output_prefix + '_pve_1' + fast_suffix + ' multiplier_noNAN.mif -mult cgm.mif') - runCommand('mrcalc ' + fast_output_prefix + '_pve_2' + fast_suffix + ' multiplier_noNAN.mif -mult wm_mask.mif -mult wm.mif') + runCommand('mrcalc ' + fast_output_prefix + '_pve_1' + fast_suffix + ' multiplier_noNAN.mif -mult remove_unconnected_wm_mask.mif -mult cgm.mif') + runCommand('mrcalc ' + fast_output_prefix + '_pve_2' + fast_suffix + ' multiplier_noNAN.mif -mult remove_unconnected_wm_mask.mif -mult wm.mif') runCommand('mrcalc 0 wm.mif -min path.mif') runCommand('mrcat cgm.mif sgm.mif wm.mif csf.mif path.mif - -axis 3 | mrconvert - combined_precrop.mif -stride +2,+3,+4,+1') From 221a44e487ca8a44381db36f6ef3c89e4a34582e Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 28 Sep 2016 17:45:31 +1000 Subject: [PATCH 165/723] New command: 5ttcheck Command checks more thoroughly that a provided image conforms to the 5TT format, by ensuring that all brain voxels (i.e. voxels where at least one tissue type is nonzero) have tissue volume fractions that sum to 1.0. This command is used at the end of the 5ttgen script (to test the output of other potential 5ttgen algorithms), and also tests the 5TT image as output to dwi2response msmt_5tt. --- cmd/5ttcheck.cpp | 121 +++++++++++++++++++++++++++ docs/reference/commands/5ttcheck.rst | 57 +++++++++++++ docs/reference/commands_list.rst | 2 + scripts/5ttgen | 5 ++ scripts/src/dwi2response/msmt_5tt.py | 5 +- 5 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 cmd/5ttcheck.cpp create mode 100644 docs/reference/commands/5ttcheck.rst diff --git a/cmd/5ttcheck.cpp b/cmd/5ttcheck.cpp new file mode 100644 index 0000000000..ba5fe279c9 --- /dev/null +++ b/cmd/5ttcheck.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + + +#include "app.h" +#include "command.h" +#include "datatype.h" +#include "image.h" +#include "image_helpers.h" + +#include "algo/copy.h" +#include "algo/loop.h" + +#include "dwi/tractography/ACT/act.h" + + +using namespace MR; +using namespace App; + + + +#define MAX_ERROR 0.001 + + + +void usage () +{ + AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; + + DESCRIPTION + + "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(); + + OPTIONS + + Option ("masks", "output mask images highlighting voxels where the input does not conform to 5TT requirements") + + Argument ("prefix").type_text(); +} + + + +void run () +{ + const std::string mask_prefix = get_option_value ("masks", ""); + + size_t error_count = 0; + for (size_t i = 0; i != argument.size(); ++i) { + + auto in = Image::open (argument[i]); + + Image voxels; + Header H_out (in); + H_out.ndim() = 3; + H_out.datatype() = DataType::Bit; + if (mask_prefix.size()) + voxels = Image::scratch (H_out, "Scratch image for " + argument[i]); + + try { + + // This checks: + // - Floating-point image + // - 4-dimensional + // - 5 volumes + 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) + size_t voxel_error_sum = 0; + for (auto outer = Loop(in, 0, 3) (in); outer; ++outer) { + default_type sum = 0.0; + for (auto inner = Loop(3) (in); inner; ++inner) + sum += in.value(); + if (!sum) continue; + if (std::abs (sum-1.0) > MAX_ERROR) { + ++voxel_error_sum; + if (voxels.valid()) { + assign_pos_of (in, 0, 3).to (voxels); + voxels.value() = true; + } + } + } + + if (voxel_error_sum) { + WARN ("Image \"" + argument[i] + "\" contains " + str(voxel_error_sum) + " brain voxels with non-unity sum of partial volume fractions"); + ++error_count; + if (voxels.valid()) { + auto out = Image::create (mask_prefix + Path::basename (argument[i]), H_out); + copy (voxels, out); + } + } else { + INFO ("Image \"" + argument[i] + "\" conforms to 5TT format"); + } + + } catch (...) { + WARN ("Image \"" + argument[i] + "\" does not conform to fundamental 5TT format requirements"); + ++error_count; + } + } + + if (error_count) { + if (argument.size() > 1) + throw Exception (str(error_count) + " input image" + (error_count > 1 ? "s do" : " does") + " not conform to 5TT format"); + else + throw Exception ("Input image does not conform to 5TT format"); + } +} + diff --git a/docs/reference/commands/5ttcheck.rst b/docs/reference/commands/5ttcheck.rst new file mode 100644 index 0000000000..7d4947b8f1 --- /dev/null +++ b/docs/reference/commands/5ttcheck.rst @@ -0,0 +1,57 @@ +.. _5ttcheck: + +5ttcheck +=========== + +Synopsis +-------- + +:: + + 5ttcheck [ options ] input [ input ... ] + +- *input*: the 5TT image(s) to be tested + +Description +----------- + +thoroughly check that one or more images conform to the expected ACT five-tissue-type (5TT) format + +Options +------- + +- **-masks prefix** output mask images highlighting voxels where the input does not conform to 5TT requirements + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) + +- **-failonwarn** terminate program if a warning is produced + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +-------------- + + + +**Author:** Robert E. Smith (robert.smith@florey.edu.au) + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index 6a26a102e6..4c0f2b3d11 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -13,6 +13,8 @@ List of MRtrix3 commands commands/5tt2vis + commands/5ttcheck + commands/5ttedit commands/afdconnectivity diff --git a/scripts/5ttgen b/scripts/5ttgen index c80eae5308..5212c805f0 100755 --- a/scripts/5ttgen +++ b/scripts/5ttgen @@ -13,6 +13,7 @@ import lib.app, lib.cmdlineParser from lib.getAlgorithmList import getAlgorithmList from lib.getUserPath import getUserPath from lib.runCommand import runCommand +from lib.warnMessage import warnMessage lib.app.author = 'Robert E. Smith (robert.smith@florey.edu.au)' lib.app.addCitation('', 'Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. Anatomically-constrained tractography: Improved diffusion MRI streamlines tractography through effective use of anatomical information. NeuroImage, 2012, 62, 1924-1938', False) @@ -49,6 +50,10 @@ lib.app.gotoTempDir() algorithm.execute() +(stdout,stderr) = runCommand('5ttcheck result.mif', False) +if len(stderr) and 'ERROR' in stderr: + warnMessage('Generated image does not perfectly conform to 5TT format') + runCommand('mrconvert result.mif ' + getUserPath(lib.app.args.output, True) + lib.app.mrtrixForce) lib.app.complete() diff --git a/scripts/src/dwi2response/msmt_5tt.py b/scripts/src/dwi2response/msmt_5tt.py index 60c54f151c..6e192f86ee 100644 --- a/scripts/src/dwi2response/msmt_5tt.py +++ b/scripts/src/dwi2response/msmt_5tt.py @@ -52,10 +52,7 @@ def execute(): # May need to commit 5ttregrid... # Verify input 5tt image - sizes = [ int(x) for x in getHeaderInfo('5tt.mif', 'size').split() ] - datatype = getHeaderInfo('5tt.mif', 'datatype') - if not len(sizes) == 4 or not sizes[3] == 5 or not datatype.startswith('Float'): - errorMessage('Imported anatomical image ' + os.path.basename(lib.app.args.in_5tt) + ' is not in the 5TT format') + runCommand('5ttcheck 5tt.mif') # Get shell information shells = [ int(round(float(x))) for x in getHeaderInfo('dwi.mif', 'shells').split() ] From 9082208b317548046562c708b0dbb4cde5779266 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 29 Sep 2016 13:54:29 +1000 Subject: [PATCH 166/723] renamed 3ttnormalise to mtnormalise --- cmd/fixelcfestats.cpp | 28 +++++----- cmd/{3ttnormalise.cpp => mtnormalise.cpp} | 0 cmd/voxel2fixel.cpp | 21 ++++---- cmd/warp2metric.cpp | 6 ++- docs/reference/commands/3ttnormalise.rst | 62 ----------------------- docs/reference/commands/voxel2fixel.rst | 10 ++-- docs/reference/commands/warp2metric.rst | 5 ++ docs/reference/commands_list.rst | 4 +- 8 files changed, 41 insertions(+), 95 deletions(-) rename cmd/{3ttnormalise.cpp => mtnormalise.cpp} (100%) delete mode 100644 docs/reference/commands/3ttnormalise.rst diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index de7a3bda21..39f1fa9f4d 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -162,7 +162,7 @@ void run() { FixelFormat::copy_index_and_directions_file (input_fixel_folder, output_fixel_folder); { - auto directions_data = FixelFormat::find_directions_header (input_fixel_folder).get_image().with_direct_io(); + auto directions_data = FixelFormat::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) { @@ -171,7 +171,7 @@ void run() { uint32_t offset = index_image.value(); size_t fixel_index = 0; for (auto f = FixelFormat::FixelLoop (index_image) (directions_data); f; ++f, ++fixel_index) { - directions[offset + fixel_index] = directions_data.row(1).cast(); + directions[offset + fixel_index] = directions_data.row(1); positions[offset + fixel_index] = image_transform.voxel2scanner * vox; } } @@ -263,9 +263,9 @@ void run() { const value_type distance = std::sqrt (Math::pow2 (positions[fixel][0] - positions[it->first][0]) + Math::pow2 (positions[fixel][1] - positions[it->first][1]) + Math::pow2 (positions[fixel][2] - positions[it->first][2])); - const float smoothing_weight = connectivity * gaussian_const1 * std::exp (-std::pow (distance, 2) / gaussian_const2); + const connectivity_value_type smoothing_weight = connectivity * gaussian_const1 * std::exp (-std::pow (distance, 2) / gaussian_const2); if (smoothing_weight > 0.01) - smoothing_weights[fixel].insert (std::pair (it->first, smoothing_weight)); + smoothing_weights[fixel].insert (std::pair (it->first, smoothing_weight)); } // Here we pre-exponentiate each connectivity value by C it->second.value = std::pow (connectivity, cfe_c); @@ -274,7 +274,7 @@ void run() { } // Make sure the fixel is fully connected to itself connectivity_matrix[fixel].insert (std::pair (fixel, Stats::CFE::connectivity (1.0))); - smoothing_weights[fixel].insert (std::pair (fixel, gaussian_const1)); + smoothing_weights[fixel].insert (std::pair (fixel, gaussian_const1)); // Normalise smoothing weights value_type sum = 0.0; @@ -301,36 +301,38 @@ void run() { // Load input data matrix_type data (num_fixels, identifiers.size()); + data.setZero(); { ProgressBar progress ("loading input images", identifiers.size()); for (size_t subject = 0; subject < identifiers.size(); subject++) { LogLevelLatch log_level (0); - auto subject_data = Image::open (identifiers[subject]).with_direct_io(); + auto subject_data = Image::open (identifiers[subject]).with_direct_io(); std::vector subject_data_vector (num_fixels, 0.0); - for (auto i = Loop (index_image)(index_image); i; ++i) { + for (auto i = Loop (index_image, 0, 3)(index_image); i; ++i) { index_image.index(3) = 1; uint32_t offset = index_image.value(); uint32_t fixel_index = 0; - for (auto f = FixelFormat::FixelLoop (index_image) (subject_data); f; ++f, ++fixel_index) + for (auto f = FixelFormat::FixelLoop (index_image) (subject_data); f; ++f, ++fixel_index) { + if (!std::isfinite(subject_data.value())) + throw Exception ("subject data file " + identifiers[subject] + " contains non-finite value: " + str(subject_data.value())); subject_data_vector[offset + fixel_index] = subject_data.value(); + } } // Smooth the data for (size_t fixel = 0; fixel < num_fixels; ++fixel) { value_type value = 0.0; - std::map::const_iterator it = smoothing_weights[fixel].begin(); - for (; it != smoothing_weights[fixel].end(); ++it) + std::map::const_iterator it = smoothing_weights[fixel].begin(); + for (; it != smoothing_weights[fixel].end(); ++it) { value += subject_data_vector[it->first] * it->second; + } data (fixel, subject) = value; } progress++; } } - if (!data.allFinite()) - throw Exception ("input data contains non-finite value(s)"); - { ProgressBar progress ("outputting beta coefficients, effect size and standard deviation"); auto temp = Math::Stats::GLM::solve_betas (data, design); diff --git a/cmd/3ttnormalise.cpp b/cmd/mtnormalise.cpp similarity index 100% rename from cmd/3ttnormalise.cpp rename to cmd/mtnormalise.cpp diff --git a/cmd/voxel2fixel.cpp b/cmd/voxel2fixel.cpp index 99333dd922..559d6b5990 100644 --- a/cmd/voxel2fixel.cpp +++ b/cmd/voxel2fixel.cpp @@ -33,13 +33,13 @@ void usage () DESCRIPTION + "map the scalar value in each voxel to all fixels within that voxel. " - "This could be used to enable CFE-based statistical analysis to be performed on voxel-wise measures"; + "This is designed to enable CFE-based statistical analysis to be performed on voxel-wise measures"; ARGUMENTS - + Argument ("image_in", "the input image.").type_image_in () - + Argument ("fixel_in", "the input fixel folder. Used to define the fixels and their directions").type_text () - + Argument ("fixel_out", "the output fixel folder. This can be the same as the input folder if desired").type_text () - + Argument ("fixel_out", "the name of the fixel data image.").type_image_out (); + + Argument ("image_in", "the input image.").type_image_in() + + Argument ("fixel_folder_in", "the input fixel folder. Used to define the fixels and their directions").type_text() + + Argument ("fixel_folder_out", "the output fixel folder. 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_image_out(); } @@ -57,11 +57,8 @@ void run () auto output_fixel_data = Image::create (argument[3], FixelFormat::data_header_from_index (input_fixel_index)); -// for (auto i = Loop ("mapping voxel scalar values to fixels", 0, 3)(scalar, input_fixel_index); i; ++i) { -// output.value().set_size (fixel_template.value().size()); -// for (size_t f = 0; f != fixel_template.value().size(); ++f) { -// output.value()[f] = fixel_template.value()[f]; -// output.value()[f].value = scalar.value(); -// } -// } + for (auto v = Loop ("mapping voxel scalar values to fixels", 0, 3)(scalar, input_fixel_index); v; ++v) { + for (auto f = FixelFormat::FixelLoop (input_fixel_index) (output_fixel_data); f; ++f) + output_fixel_data.value() = scalar.value(); + } } diff --git a/cmd/warp2metric.cpp b/cmd/warp2metric.cpp index dfd0d8b6ce..b1bbf5e4e3 100644 --- a/cmd/warp2metric.cpp +++ b/cmd/warp2metric.cpp @@ -35,6 +35,11 @@ void usage () DESCRIPTION + "compute fixel or voxel-wise metrics from a 4D deformation field"; + 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. \n" + "Neuroimage, 2016, 10.1016/j.neuroimage.2016.09.029\n"; + ARGUMENTS + Argument ("in", "the input deformation field").type_image_in(); @@ -53,7 +58,6 @@ void usage () + Option ("jdet", "output the Jacobian determinant instead of the full matrix") + Argument ("output").type_image_out(); - //TODO add FC paper reference } diff --git a/docs/reference/commands/3ttnormalise.rst b/docs/reference/commands/3ttnormalise.rst deleted file mode 100644 index a1a901df47..0000000000 --- a/docs/reference/commands/3ttnormalise.rst +++ /dev/null @@ -1,62 +0,0 @@ -.. _3ttnormalise: - -3ttnormalise -=========== - -Synopsis --------- - -:: - - 3ttnormalise [ options ] wm gm csf wm_out gm_out csf_out - -- *wm*: the white matter compartment (FOD) image -- *gm*: the grey matter compartment image -- *csf*: the cerebral spinal fluid comparment image -- *wm_out*: the output white matter compartment (FOD) image -- *gm_out*: the output grey matter compartment image -- *csf_out*: the output cerebral spinal fluid comparment image - -Description ------------ - -Globally normalise three tissue (wm, gm, csf) comparments from multi-tissue CSD such that their sum within each voxel is as close to 1 as possible. This involves solving for a single scale factor for each compartment map. - -Options -------- - -- **-mask image** define the mask to compute the normalisation within. If not supplied this is estimated automatically - -Standard options -^^^^^^^^^^^^^^^^ - -- **-info** display information messages. - -- **-quiet** do not display information messages or progress status. - -- **-debug** display debugging messages. - -- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. - -- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) - -- **-failonwarn** terminate program if a warning is produced - -- **-help** display this information page and exit. - -- **-version** display version information and exit. - --------------- - - - -**Author:** David Raffelt (david.raffelt@florey.edu.au) - -**Copyright:** Copyright (c) 2008-2016 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/ - -MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -For more details, see www.mrtrix.org - diff --git a/docs/reference/commands/voxel2fixel.rst b/docs/reference/commands/voxel2fixel.rst index 387ef22355..3664898336 100644 --- a/docs/reference/commands/voxel2fixel.rst +++ b/docs/reference/commands/voxel2fixel.rst @@ -8,17 +8,17 @@ Synopsis :: - voxel2fixel [ options ] image_in fixel_in fixel_out fixel_out + voxel2fixel [ options ] image_in fixel_folder_in fixel_folder_out fixel_data_out - *image_in*: the input image. -- *fixel_in*: the input fixel folder. Used to define the fixels and their directions -- *fixel_out*: the output fixel folder. This can be the same as the input folder if desired -- *fixel_out*: the name of the fixel data image. +- *fixel_folder_in*: the input fixel folder. Used to define the fixels and their directions +- *fixel_folder_out*: the output fixel folder. This can be the same as the input folder if desired +- *fixel_data_out*: the name of the fixel data image. Description ----------- -map the scalar value in each voxel to all fixels within that voxel. This could be used to enable CFE-based statistical analysis to be performed on voxel-wise measures +map the scalar value in each voxel to all fixels within that voxel. This is designed to enable CFE-based statistical analysis to be performed on voxel-wise measures Options ------- diff --git a/docs/reference/commands/warp2metric.rst b/docs/reference/commands/warp2metric.rst index e80f1e65f5..8880e8ac9c 100644 --- a/docs/reference/commands/warp2metric.rst +++ b/docs/reference/commands/warp2metric.rst @@ -45,6 +45,11 @@ Standard options - **-version** display version information and exit. +References +^^^^^^^^^^ + +Raffelt, D.; Tournier, JD/; Smith, RE.; Vaughan, DN.; Jackson, G.; Ridgway, GR. Connelly, A.Investigating White Matter Fibre Density and Morphology using Fixel-Based Analysis. Neuroimage, 2016, 10.1016/j.neuroimage.2016.09.029 + -------------- diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index 00bfc2aaed..f461dac56f 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -9,8 +9,6 @@ List of MRtrix3 commands - commands/3ttnormalise - commands/5tt2gmwmi commands/5tt2vis @@ -135,6 +133,8 @@ List of MRtrix3 commands commands/mrview + commands/mtnormalise + commands/peaks2amp commands/sh2amp From c656b03d8d4f20b52c92b7f4c5dcd7514d8d7e48 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 29 Sep 2016 13:54:51 +1000 Subject: [PATCH 167/723] added command doc for mrnormalise --- docs/reference/commands/mtnormalise.rst | 62 +++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 docs/reference/commands/mtnormalise.rst diff --git a/docs/reference/commands/mtnormalise.rst b/docs/reference/commands/mtnormalise.rst new file mode 100644 index 0000000000..f7015b960c --- /dev/null +++ b/docs/reference/commands/mtnormalise.rst @@ -0,0 +1,62 @@ +.. _mtnormalise: + +mtnormalise +=========== + +Synopsis +-------- + +:: + + mtnormalise [ options ] wm gm csf wm_out gm_out csf_out + +- *wm*: the white matter compartment (FOD) image +- *gm*: the grey matter compartment image +- *csf*: the cerebral spinal fluid comparment image +- *wm_out*: the output white matter compartment (FOD) image +- *gm_out*: the output grey matter compartment image +- *csf_out*: the output cerebral spinal fluid comparment image + +Description +----------- + +Globally normalise three tissue (wm, gm, csf) comparments from multi-tissue CSD such that their sum within each voxel is as close to 1 as possible. This involves solving for a single scale factor for each compartment map. + +Options +------- + +- **-mask image** define the mask to compute the normalisation within. If not supplied this is estimated automatically + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) + +- **-failonwarn** terminate program if a warning is produced + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +-------------- + + + +**Author:** David Raffelt (david.raffelt@florey.edu.au) + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + From a8b9004a3f77ea394b8469d23941df47b0700615 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 29 Sep 2016 15:31:20 +1000 Subject: [PATCH 168/723] Ported voxel2fixel command --- cmd/voxel2fixel.cpp | 12 ++++++++---- docs/reference/commands/voxel2fixel.rst | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cmd/voxel2fixel.cpp b/cmd/voxel2fixel.cpp index 559d6b5990..5689a3eec2 100644 --- a/cmd/voxel2fixel.cpp +++ b/cmd/voxel2fixel.cpp @@ -37,9 +37,9 @@ void usage () ARGUMENTS + Argument ("image_in", "the input image.").type_image_in() - + Argument ("fixel_folder_in", "the input fixel folder. Used to define the fixels and their directions").type_text() + + Argument ("fixel_folder", "the input fixel folder. Used to define the fixels and their directions").type_text() + Argument ("fixel_folder_out", "the output fixel folder. 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_image_out(); + + Argument ("fixel_data_out", "the name of the fixel data image.").type_image_out(); } @@ -52,10 +52,14 @@ void run () check_dimensions (scalar, input_fixel_index, 0, 3); std::string output_fixel_folder = argument[2]; - if (input_fixel_folder != output_fixel_folder) + if (input_fixel_folder != output_fixel_folder) { + ProgressBar progress ("copying fixel index and directions file into output folder"); + progress++; FixelFormat::copy_index_and_directions_file (input_fixel_folder, output_fixel_folder); + progress++; + } - auto output_fixel_data = Image::create (argument[3], FixelFormat::data_header_from_index (input_fixel_index)); + auto output_fixel_data = Image::create (Path::join(output_fixel_folder, argument[3]), FixelFormat::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 = FixelFormat::FixelLoop (input_fixel_index) (output_fixel_data); f; ++f) diff --git a/docs/reference/commands/voxel2fixel.rst b/docs/reference/commands/voxel2fixel.rst index 3664898336..c471b0e929 100644 --- a/docs/reference/commands/voxel2fixel.rst +++ b/docs/reference/commands/voxel2fixel.rst @@ -8,10 +8,10 @@ Synopsis :: - voxel2fixel [ options ] image_in fixel_folder_in fixel_folder_out fixel_data_out + voxel2fixel [ options ] image_in fixel_folder fixel_folder_out fixel_data_out - *image_in*: the input image. -- *fixel_folder_in*: the input fixel folder. Used to define the fixels and their directions +- *fixel_folder*: the input fixel folder. Used to define the fixels and their directions - *fixel_folder_out*: the output fixel folder. This can be the same as the input folder if desired - *fixel_data_out*: the name of the fixel data image. From 0a2f5bb5008efc0c018452f51ff8173b734fc3ff Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 29 Sep 2016 15:50:21 +1000 Subject: [PATCH 169/723] New commands: maskdump and mredit - maskdump writes the voxel locations of all non-zero voxels to stdout. It may somewhat compensate for the old -position option in mrstats. - mredit enables editing of voxel intensities at the command-line. --- cmd/maskdump.cpp | 57 ++++++++ cmd/mredit.cpp | 203 +++++++++++++++++++++++++++ docs/reference/commands/maskdump.rst | 55 ++++++++ docs/reference/commands/mredit.rst | 58 ++++++++ docs/reference/commands_list.rst | 4 + 5 files changed, 377 insertions(+) create mode 100644 cmd/maskdump.cpp create mode 100644 cmd/mredit.cpp create mode 100644 docs/reference/commands/maskdump.rst create mode 100644 docs/reference/commands/mredit.rst diff --git a/cmd/maskdump.cpp b/cmd/maskdump.cpp new file mode 100644 index 0000000000..b965cb0df4 --- /dev/null +++ b/cmd/maskdump.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + +#include "command.h" +#include "image.h" +#include "algo/loop.h" + +using namespace MR; +using namespace App; + + +void usage () +{ + AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; + + DESCRIPTION + + "Print out the locations of all non-zero voxels in a mask image"; + + ARGUMENTS + + Argument ("input", "the input image.").type_image_in(); +} + + +void run () +{ + auto H = Header::open (argument[0]); + if (H.datatype() != DataType::Bit) + WARN ("Input is not a genuine boolean mask image"); + auto in = H.get_image(); + std::vector< Eigen::ArrayXi > locations; + for (auto l = Loop(in) (in); l; ++l) { + if (in.value()) { + 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)); + } + } + 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"); + std::cout << prettyprint; +} diff --git a/cmd/mredit.cpp b/cmd/mredit.cpp new file mode 100644 index 0000000000..0636f7b713 --- /dev/null +++ b/cmd/mredit.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#include +#include + +#include "command.h" +#include "image.h" +#include "image_helpers.h" +#include "transform.h" +#include "algo/copy.h" + +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 () +{ + AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; + + DESCRIPTION + + "Directly edit the intensities within an image from the command-line. " + + "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."; + + ARGUMENTS + + 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 ("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::Vector3& 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} }; + + + +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 + Image out; + if (inplace) { + 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); + } + + Transform transform (H); + const bool scanner = get_options ("scanner").size(); + + size_t operation_count = 0; + + auto opt = get_options ("plane"); + if (opt.size()) { + if (H.ndim() != 3) + throw Exception ("-plane option only works for 3D images"); + if (scanner) + 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 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) }; + 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.value() = value; + } + } + } + + opt = get_options ("sphere"); + if (opt.size() && H.ndim() != 3) + 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::Vector3 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::Vector3 centre_voxelspace (centre_scannerspace); + if (scanner) + centre_voxelspace = transform.scanner2voxel * centre_scannerspace; + else + centre_scannerspace = transform.voxel2scanner * centre_voxelspace; + std::set processed; + std::vector to_expand; + 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()); + to_expand.pop_back(); + const Eigen::Vector3 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); + 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); + } + } + } + } + } + + opt = get_options ("voxel"); + operation_count += opt.size(); + for (auto v : opt) { + 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"); + Eigen::Vector3 p (position[0], position[1], position[2]); + if (scanner) + p = transform.scanner2voxel * p; + const Vox voxel (p); + assign_pos_of (voxel).to (out); + out.value() = value; + } + + if (!operation_count) { + if (inplace) { + WARN ("No edits specified; image will be unaffected"); + } else { + WARN ("No edits specified; output image will be copy of input"); + } + } +} diff --git a/docs/reference/commands/maskdump.rst b/docs/reference/commands/maskdump.rst new file mode 100644 index 0000000000..8d7550db1c --- /dev/null +++ b/docs/reference/commands/maskdump.rst @@ -0,0 +1,55 @@ +.. _maskdump: + +maskdump +=========== + +Synopsis +-------- + +:: + + maskdump [ options ] input + +- *input*: the input image. + +Description +----------- + +Print out the locations of all non-zero voxels in a mask image + +Options +------- + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) + +- **-failonwarn** terminate program if a warning is produced + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +-------------- + + + +**Author:** Robert E. Smith (robert.smith@florey.edu.au) + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + diff --git a/docs/reference/commands/mredit.rst b/docs/reference/commands/mredit.rst new file mode 100644 index 0000000000..203d2bb2e7 --- /dev/null +++ b/docs/reference/commands/mredit.rst @@ -0,0 +1,58 @@ +.. _mredit: + +mredit +=========== + +Synopsis +-------- + +:: + + mredit [ options ] input[ output ] + +- *input*: the input image +- *output*: the (optional) output image + +Description +----------- + +Edit the intensities within an image from the command-line. If only one image path is provided, the image will be edited in-place. If input and output image paths are provided, the original image will not be modified. + +Options +------- + +- **-voxel position value** Change the intensity of a single voxel + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) + +- **-failonwarn** terminate program if a warning is produced + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +-------------- + + + +**Author:** Robert E. Smith (robert.smith@florey.edu.au) + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index 4c0f2b3d11..946d815995 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -89,6 +89,8 @@ List of MRtrix3 commands commands/labelconvert + commands/maskdump + commands/maskfilter commands/mesh2pve @@ -111,6 +113,8 @@ List of MRtrix3 commands commands/mrcrop + commands/mredit + commands/mrfilter commands/mrhistmatch From 0cd7f37fcf94e7c52536edc8627d055f3121f03e Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Sun, 2 Oct 2016 12:48:01 +1100 Subject: [PATCH 170/723] Enabled mtnormalise to handle any number of input tissue types --- cmd/mtnormalise.cpp | 93 +++++++++++++++---------- cmd/voxel2fixel.cpp | 2 +- docs/reference/commands/mtnormalise.rst | 13 ++-- docs/reference/commands/voxel2fixel.rst | 4 +- 4 files changed, 64 insertions(+), 48 deletions(-) diff --git a/cmd/mtnormalise.cpp b/cmd/mtnormalise.cpp index 3db32ae1af..4def5c2d23 100644 --- a/cmd/mtnormalise.cpp +++ b/cmd/mtnormalise.cpp @@ -24,54 +24,65 @@ using namespace MR; using namespace App; - +#define DEFAULT_NORM_VALUE 0.282094 void usage () { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; DESCRIPTION - + "Globally normalise three tissue (wm, gm, csf) comparments from multi-tissue CSD " - "such that their sum within each voxel is as close to 1 as possible. " - "This involves solving for a single scale factor for each compartment map."; + + "Multi-tissue normalise. Globally normalise multiple tissue compartments (e.g. WM, GM, CSF) " + "from multi-tissue CSD such that their sum (of their DC terms) within each voxel is as close to a scalar as possible " + "(Default: sqrt(1/(4*pi)). Normalisation is performed by solving for a single scale factor to adjust each tissue type. \n\n" + "Example usage: mtnormalise wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif"; ARGUMENTS - + Argument ("wm", "the white matter compartment (FOD) image").type_image_in() - + Argument ("gm", "the grey matter compartment image").type_image_in() - + Argument ("csf", "the cerebral spinal fluid comparment image").type_image_in() - + Argument ("wm_out", "the output white matter compartment (FOD) image").type_image_out() - + Argument ("gm_out", "the output grey matter compartment image").type_image_out() - + Argument ("csf_out", "the output cerebral spinal fluid comparment image").type_image_out(); + + Argument ("input output", "list of all input and output tissue compartment files. See example usage in the description. " + "Note that any number of tissues can be normalised").type_image_in().allow_multiple(); OPTIONS + Option ("mask", "define the mask to compute the normalisation within. If not supplied this is estimated automatically") - + Argument ("image").type_image_in (); + + Argument ("image").type_image_in () + + Option ("value", "specify the value to which the summed tissue compartments will be to (Default: sqrt(1/(4*pi) = " + str(DEFAULT_NORM_VALUE, 3) + ")") + + Argument ("number").type_float (); } void run () { - auto wm = Image::open (argument[0]); - auto gm = Image::open (argument[1]); - auto csf = Image::open (argument[2]); - - check_dimensions (wm, gm, 0, 3); - check_dimensions (csf, gm); - - auto wm_out = Image::create (argument[3], wm); - auto gm_out = Image::create (argument[4], gm); - auto csf_out = Image::create (argument[5], csf); - auto wm_dc = Adapter::make (wm, 3, std::vector (1, 0)); + if (argument.size() % 2) + throw Exception ("The number of input arguments must be even. There must be an output file provided for every input tissue image"); + + std::vector> input_images; + std::vector> output_images; + + std::vector sh_image_indexes; + for (size_t i = 0; i < argument.size(); i += 2) { + Header header = Header::open (argument[i]); + if (header.ndim() == 4 && header.size(3) > 1) { // assume SH image to extract DC term + auto dc = Adapter::make (header.get_image(), 3, std::vector (1, 0)); + input_images.emplace_back (Image::scratch(dc)); + threaded_copy_with_progress_message ("loading image", dc, input_images[i / 2]); + sh_image_indexes.push_back(i / 2); + } else { + input_images.emplace_back (Image::open(argument[i])); + } + if (i) + check_dimensions (input_images[0], input_images[i / 2], 0, 3); + output_images.emplace_back (Image::create (argument[i + 1], header)); + } Image mask; auto opt = get_options("mask"); if (opt.size()) { mask = Image::open (opt[0][0]); } else { - auto summed = Image::scratch (gm); - for (auto i = Loop (gm) (gm, csf, wm_dc, summed); i; ++i) - summed.value() = gm.value() + csf.value() + wm_dc.value(); + auto summed = Image::scratch (input_images[0]); + for (size_t j = 0; j < input_images.size(); ++j) { + for (auto i = Loop (summed) (summed, input_images[j]); i; ++i) + summed.value() += input_images[j].value(); + } Filter::OptimalThreshold threshold_filter (summed); mask = Image::scratch (threshold_filter); threshold_filter (summed, mask); @@ -82,29 +93,37 @@ void run () num_voxels++; } + const float normalisation_value = get_option_value ("value", DEFAULT_NORM_VALUE); + { ProgressBar progress ("normalising tissue compartments..."); - Eigen::MatrixXf X (num_voxels, 3); + Eigen::MatrixXf X (num_voxels, input_images.size()); Eigen::MatrixXf y (num_voxels, 1); - y.setOnes(); + y.fill (normalisation_value); size_t counter = 0; - for (auto i = Loop (gm) (gm, csf, wm_dc, mask); i; ++i) { + for (auto i = Loop (mask) (mask); i; ++i) { if (mask.value()) { - X (counter, 0) = gm.value(); - X (counter, 1) = csf.value(); - X (counter++, 2) = wm_dc.value(); + for (size_t j = 0; j < input_images.size(); ++j) { + assign_pos_of (mask, 0, 3).to (input_images[j]); + X (counter, j) = input_images[j].value(); + } + ++counter; } } progress++; Eigen::MatrixXf w = X.jacobiSvd (Eigen::ComputeThinU | Eigen::ComputeThinV).solve(y); progress++; - for (auto i = Loop (gm) (gm, csf, gm_out, csf_out); i; ++i) { - gm_out.value() = gm.value() * w(0,0); - csf_out.value() = csf.value() * w(1,0); + for (size_t j = 0; j < input_images.size(); ++j) { + if (std::find (sh_image_indexes.begin(), sh_image_indexes.end(), j) != sh_image_indexes.end()) { + auto input = Image::open (argument[j * 2]); + for (auto i = Loop (input) (input, output_images[j]); i; ++i) + output_images[j].value() = input.value() * w(j,0); + } else { + for (auto i = Loop (input_images[j]) (input_images[j], output_images[j]); i; ++i) + output_images[j].value() = input_images[j].value() * w(j,0); + } + progress++; } - progress++; - for (auto i = Loop (wm) (wm, wm_out); i; ++i) - wm_out.value() = wm.value() * w(2,0); } } diff --git a/cmd/voxel2fixel.cpp b/cmd/voxel2fixel.cpp index 5689a3eec2..f8be23d500 100644 --- a/cmd/voxel2fixel.cpp +++ b/cmd/voxel2fixel.cpp @@ -37,7 +37,7 @@ void usage () ARGUMENTS + Argument ("image_in", "the input image.").type_image_in() - + Argument ("fixel_folder", "the input fixel folder. Used to define the fixels and their directions").type_text() + + Argument ("fixel_folder_in", "the input fixel folder. Used to define the fixels and their directions").type_text() + Argument ("fixel_folder_out", "the output fixel folder. 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_image_out(); } diff --git a/docs/reference/commands/mtnormalise.rst b/docs/reference/commands/mtnormalise.rst index f7015b960c..03a7a405ad 100644 --- a/docs/reference/commands/mtnormalise.rst +++ b/docs/reference/commands/mtnormalise.rst @@ -8,25 +8,22 @@ Synopsis :: - mtnormalise [ options ] wm gm csf wm_out gm_out csf_out + mtnormalise [ options ] input output [ input output ... ] -- *wm*: the white matter compartment (FOD) image -- *gm*: the grey matter compartment image -- *csf*: the cerebral spinal fluid comparment image -- *wm_out*: the output white matter compartment (FOD) image -- *gm_out*: the output grey matter compartment image -- *csf_out*: the output cerebral spinal fluid comparment image +- *input output*: list of all input and output tissue compartment files. See example usage in the description. Note that any number of tissues can be normalised Description ----------- -Globally normalise three tissue (wm, gm, csf) comparments from multi-tissue CSD such that their sum within each voxel is as close to 1 as possible. This involves solving for a single scale factor for each compartment map. +Multi-tissue normalise. Globally normalise multiple tissue compartments (e.g. WM, GM, CSF) from multi-tissue CSD such that their sum (of their DC terms) within each voxel is as close to a scalar as possible (Default: sqrt(1/(4*pi)). Normalisation is performed by solving for a single scale factor to adjust each tissue type. Example usage: mtnormalise wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif Options ------- - **-mask image** define the mask to compute the normalisation within. If not supplied this is estimated automatically +- **-value number** specify the value to which the summed tissue compartments will be to (Default: sqrt(1/(4*pi) = 0.282) + Standard options ^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/voxel2fixel.rst b/docs/reference/commands/voxel2fixel.rst index c471b0e929..3664898336 100644 --- a/docs/reference/commands/voxel2fixel.rst +++ b/docs/reference/commands/voxel2fixel.rst @@ -8,10 +8,10 @@ Synopsis :: - voxel2fixel [ options ] image_in fixel_folder fixel_folder_out fixel_data_out + voxel2fixel [ options ] image_in fixel_folder_in fixel_folder_out fixel_data_out - *image_in*: the input image. -- *fixel_folder*: the input fixel folder. Used to define the fixels and their directions +- *fixel_folder_in*: the input fixel folder. Used to define the fixels and their directions - *fixel_folder_out*: the output fixel folder. This can be the same as the input folder if desired - *fixel_data_out*: the name of the fixel data image. From 45de3723aeb939b5eab534e232d735c64213d7aa Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Sun, 2 Oct 2016 16:46:10 +1100 Subject: [PATCH 171/723] initial commit of mtbin script. Still some work to do --- cmd/mtnormalise.cpp | 6 +-- docs/reference/scripts/mtbin.rst | 70 +++++++++++++++++++++++++ docs/reference/scripts_list.rst | 2 + scripts/mtbin | 88 ++++++++++++++++++++++++++++++++ 4 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 docs/reference/scripts/mtbin.rst create mode 100755 scripts/mtbin diff --git a/cmd/mtnormalise.cpp b/cmd/mtnormalise.cpp index 4def5c2d23..687868c042 100644 --- a/cmd/mtnormalise.cpp +++ b/cmd/mtnormalise.cpp @@ -78,9 +78,9 @@ void run () if (opt.size()) { mask = Image::open (opt[0][0]); } else { - auto summed = Image::scratch (input_images[0]); + auto summed = Image::scratch (input_images[0], "summed image"); for (size_t j = 0; j < input_images.size(); ++j) { - for (auto i = Loop (summed) (summed, input_images[j]); i; ++i) + for (auto i = Loop (summed, 0, 3) (summed, input_images[j]); i; ++i) summed.value() += input_images[j].value(); } Filter::OptimalThreshold threshold_filter (summed); @@ -100,7 +100,7 @@ void run () Eigen::MatrixXf X (num_voxels, input_images.size()); Eigen::MatrixXf y (num_voxels, 1); y.fill (normalisation_value); - size_t counter = 0; + uint32_t counter = 0; for (auto i = Loop (mask) (mask); i; ++i) { if (mask.value()) { for (size_t j = 0; j < input_images.size(); ++j) { diff --git a/docs/reference/scripts/mtbin.rst b/docs/reference/scripts/mtbin.rst new file mode 100644 index 0000000000..91bb7b1785 --- /dev/null +++ b/docs/reference/scripts/mtbin.rst @@ -0,0 +1,70 @@ +.. _mtbin: + +mtbin +===== + +Synopsis +-------- + +:: + + mtbin [ options ] input_output + +- *input_output*: list of all input and output tissue compartment files. See example usage in the description. Note that any number of tissues can be normalised + +Description +----------- + +Multi-Tissue Bias field correction and Intensity Normalisation (MTBIN). This script inputs N number of tissue components (from multi-tissue CSD), and outputs N corrected tissue components. Intensity normalisation is performed using a single global scale factor for each tissue type. Example usage: mtbin wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif + +Options +------- + +Options for the mtbin script +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-mask** define the mask to compute the normalisation within. If not supplied this is estimated automatically + +- **-value** specify the value to which the summed tissue compartments will be globally normalised to (Default: sqrt(1/(4*pi) = 0.282) + +- **-iter** bias field correction and intensity normalisation is performed iteratively. Specify the number of iterations (Default: 5) + +Standard options +^^^^^^^^^^^^^^^^ + +- **-continue ** Continue the script from a previous execution; must provide the temporary directory path, and the name of the last successfully-generated file + +- **-force** Force overwrite of output files if pre-existing + +- **-help** Display help information for the script + +- **-nocleanup** Do not delete temporary files during script, or temporary directory at script completion + +- **-nthreads number** Use this number of threads in MRtrix multi-threaded applications (0 disables multi-threading) + +- **-tempdir /path/to/tmp/** Manually specify the path in which to generate the temporary directory + +- **-quiet** Suppress all console output during script execution + +- **-verbose** Display additional information and progress for every command invoked + +- **-debug** Display additional debugging information over and above the verbose output + +-------------- + + + +**Author:** David Raffelt (david.raffelt@florey.edu.au) + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + diff --git a/docs/reference/scripts_list.rst b/docs/reference/scripts_list.rst index bebe3131c3..26548fb0fd 100644 --- a/docs/reference/scripts_list.rst +++ b/docs/reference/scripts_list.rst @@ -21,4 +21,6 @@ Python scripts provided with MRtrix3 scripts/labelsgmfix + scripts/mtbin + scripts/population_template diff --git a/scripts/mtbin b/scripts/mtbin new file mode 100755 index 0000000000..dad41c6dfb --- /dev/null +++ b/scripts/mtbin @@ -0,0 +1,88 @@ +#!/usr/bin/env python + +import math, os, sys +import lib.app, lib.cmdlineParser + +def abspath(*arg): + return os.path.abspath(os.path.join(*arg)) + +def relpath(*arg): + return os.path.relpath(os.path.join(*arg),lib.app.workingDir) + +from lib.printMessage import printMessage +from lib.errorMessage import errorMessage +from lib.getHeaderInfo import getHeaderInfo +from lib.getUserPath import getUserPath +from lib.runCommand import runCommand + +lib.app.author = 'David Raffelt (david.raffelt@florey.edu.au)' +lib.cmdlineParser.initialise('Multi-Tissue Bias field correction and Intensity Normalisation (MTBIN). This script inputs N number of tissue components ' + '(from multi-tissue CSD), and outputs N corrected tissue components. Intensity normalisation is performed using a single global scale factor for each tissue type. ' + 'Example usage: mtbin wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif') +lib.app.parser.add_argument('input_output', nargs='*', help='list of all input and output tissue compartment files. See example usage in the description. ' + 'Note that any number of tissues can be normalised') +options = lib.app.parser.add_argument_group('Options for the mtbin script') +options.add_argument('-mask', help='define the mask to compute the normalisation within. If not supplied this is estimated automatically') +options.add_argument('-value', help='specify the value to which the summed tissue compartments will be globally normalised to (Default: sqrt(1/(4*pi) = 0.282)') +options.add_argument('-iter', default='4', help='bias field correction and intensity normalisation is performed iteratively. Specify the number of iterations (Default: 4)') +lib.app.parser.add_argument('-bias', help='Output estimated bias field') + + +lib.app.initialise() + +masking = '' +if lib.app.args.mask: + masking = '-mask ' + relpath(lib.app.args.mask_dir) + +max_iterations = 4 +if lib.app.args.iter: + normalise_value = int(lib.app.args.iter) + +if (len(lib.app.args.input_output) % 2): + errorMessage ("The number of input arguments must be even. There must be an output file provided for every input tissue image") + +lib.app.makeTempDir() + +input_files = [] +output_files = [] +for i in range(0, len(lib.app.args.input_output)): + if i % 2: + lib.app.checkOutputFile(lib.app.args.input_output[i]) + output_files.append(lib.app.args.input_output[i]) + else: + input_files.append(os.path.basename(lib.app.args.input_output[i])) + runCommand('cp -r ' + lib.app.args.input_output[i] + ' ' + lib.app.tempDir) +lib.app.gotoTempDir() + +files_to_normalise = input_files + +for iteration in range(1, max_iterations + 1): + printMessage('iteration: ' + str(iteration)) + norm_files = ['norm_' + s for s in input_files] + runCommand('mtnormalise -force ' + ' '.join([x for t in zip(files_to_normalise, norm_files) for x in t])) + files_to_sum = [] + for f in norm_files: + sizes = getHeaderInfo(f, 'size').split() + if len(sizes) > 3 and int(sizes[3]) > 1: + dc_file_name = 'dc_' + f + runCommand('mrconvert -force -coord 3 0 ' + f + ' ' + dc_file_name) + files_to_sum.append(dc_file_name) + else: + files_to_sum.append(f) + runCommand('mrmath ' + ' '.join(files_to_sum) + ' sum summed.mif -force') + runCommand('mrmodelfield ' + summed.mif + ' field.mif -force') + + if iteration < max_iterations: + files_to_bias_correct = input_files + else: + files_to_bias_correct = norm_files + + bias_corrected_files = ['bias_' + s for s in input_files] + for a, b in zip(files_to_bias_correct, bias_corrected_files): + runCommand('mrcalc -force ' + a + ' field.mif -divide ' + b) + files_to_normalise = bias_corrected_files + +for a, b in zip(bias_corrected_files, output_files): + runCommand('mrconvert ' + a + ' ' + getUserPath(b, True) + lib.app.mrtrixForce) + +lib.app.complete() From 342d59c61c4f3d6efeb2526a903b0315b6aab400 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Sun, 2 Oct 2016 16:46:50 +1100 Subject: [PATCH 172/723] fix issue in mtbin script --- scripts/mtbin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/mtbin b/scripts/mtbin index dad41c6dfb..aac952f056 100755 --- a/scripts/mtbin +++ b/scripts/mtbin @@ -70,7 +70,7 @@ for iteration in range(1, max_iterations + 1): else: files_to_sum.append(f) runCommand('mrmath ' + ' '.join(files_to_sum) + ' sum summed.mif -force') - runCommand('mrmodelfield ' + summed.mif + ' field.mif -force') + runCommand('mrmodelfield summed.mif field.mif -force') if iteration < max_iterations: files_to_bias_correct = input_files From 2ed8f7f46c0b817ec5302eda009291a812c756ab Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 3 Oct 2016 05:31:21 +1100 Subject: [PATCH 173/723] labelconvert: Fix segfault Error arises when nodes in input images are omitted from output image. --- src/connectome/lut.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/connectome/lut.cpp b/src/connectome/lut.cpp index 6a436b7294..490b6a2bf9 100644 --- a/src/connectome/lut.cpp +++ b/src/connectome/lut.cpp @@ -289,7 +289,9 @@ void LUT::check_and_insert (const node_t index, const LUT_node& data) std::vector get_lut_mapping (const LUT& in, const LUT& out) { - std::vector map; + if (in.empty()) + return std::vector(); + std::vector map (in.rbegin()->first + 1, 0); for (const auto& node_in : in) { node_t target = 0; for (const auto& node_out : out) { @@ -299,13 +301,11 @@ std::vector get_lut_mapping (const LUT& in, const LUT& out) return std::vector (); } target = node_out.first; + break; } } - if (target) { - if (node_in.first >= map.size()) - map.resize (node_in.first + 1, 0); + if (target) map[node_in.first] = target; - } } return map; } From c78460664eb6838b0d0e21524f3fe5dbbc5dfda8 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 3 Oct 2016 06:22:21 +1100 Subject: [PATCH 174/723] maskdump: Option to specify target output file --- cmd/maskdump.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/cmd/maskdump.cpp b/cmd/maskdump.cpp index b965cb0df4..6276b1e69d 100644 --- a/cmd/maskdump.cpp +++ b/cmd/maskdump.cpp @@ -27,10 +27,14 @@ void usage () AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; DESCRIPTION - + "Print out the locations of all non-zero voxels in a mask image"; + + "Print out the locations of all non-zero voxels in a mask image. " + + "If no destination file is specified, the voxel locations will be " + "printed to stdout."; ARGUMENTS - + Argument ("input", "the input image.").type_image_in(); + + Argument ("input", "the input image.").type_image_in() + + Argument ("output", "The (optional) output text file.").type_file_out().optional(); } @@ -53,5 +57,8 @@ void run () 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"); - std::cout << prettyprint; + if (argument.size() == 2) + save_matrix (prettyprint, argument[1]); + else + std::cout << prettyprint; } From 9f40c1f095b02f634732c3235d3e9fcf40d18ff8 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 3 Oct 2016 06:23:51 +1100 Subject: [PATCH 175/723] tck2connectome: Fix outdated warning text --- cmd/tck2connectome.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/tck2connectome.cpp b/cmd/tck2connectome.cpp index 6f52632561..9fb0d573da 100644 --- a/cmd/tck2connectome.cpp +++ b/cmd/tck2connectome.cpp @@ -108,7 +108,7 @@ void run () for (++i; i != missing_nodes.end(); ++i) list += ", " + str(*i); WARN (list); - WARN ("(This may indicate poor parcellation image preparation, use of incorrect config file in labelconfig, or very poor registration)"); + WARN ("(This may indicate poor parcellation image preparation, use of incorrect or incomplete LUT file(s) in labelconvert, or very poor registration)"); } // Are we generating a matrix or a vector? From 35fba5fdd53bc6e604d4f46a0ef0a5de3feb7bca Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Mon, 3 Oct 2016 20:02:08 +1100 Subject: [PATCH 176/723] Fix bug in recently ported dwi2response script --- scripts/src/dwi2response/tournier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/src/dwi2response/tournier.py b/scripts/src/dwi2response/tournier.py index 105f79d483..dc7d09feeb 100644 --- a/scripts/src/dwi2response/tournier.py +++ b/scripts/src/dwi2response/tournier.py @@ -73,7 +73,7 @@ def execute(): runCommand('mrconvert ' + prefix + 'amps.mif ' + prefix + 'second_peaks.mif -coord 3 1 -axes 0,1,2') delFile(prefix + 'amps.mif') runCommand('fixel2voxel ' + prefix + 'fixel/directions.mif split_dir ' + prefix + 'all_dirs.mif') - delfolder(prefix + 'fixel') + delFolder(prefix + 'fixel') runCommand('mrconvert ' + prefix + 'all_dirs.mif ' + prefix + 'first_dir.mif -coord 3 0:2') delFile(prefix + 'all_dirs.mif') # Calculate the 'cost function' Donald derived for selecting single-fibre voxels From 9c939f7633b3219746b95583730740cbd1f630fd Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Mon, 3 Oct 2016 20:07:48 +1100 Subject: [PATCH 177/723] Fix bug in recently ported dwi2response script --- scripts/src/dwi2response/tournier.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/scripts/src/dwi2response/tournier.py b/scripts/src/dwi2response/tournier.py index dc7d09feeb..da43885dbd 100644 --- a/scripts/src/dwi2response/tournier.py +++ b/scripts/src/dwi2response/tournier.py @@ -12,9 +12,9 @@ def initParser(subparsers, base_parser): options.add_argument('-max_iters', type=int, default=10, help='Maximum number of iterations') parser.set_defaults(algorithm='tournier') parser.set_defaults(single_shell=True) - - - + + + def checkOutputFiles(): import lib.app lib.app.checkOutputFile(lib.app.args.output) @@ -29,12 +29,12 @@ def getInputFiles(): def execute(): import os, shutil import lib.app - from lib.delFile import delFile + from lib.delFile import delFile, delFolder from lib.getImageStat import getImageStat from lib.getUserPath import getUserPath from lib.printMessage import printMessage from lib.runCommand import runCommand - + lmax_option = '' if lib.app.args.lmax: lmax_option = ' -lmax ' + lib.app.args.lmax @@ -46,7 +46,7 @@ def execute(): for iteration in range(0, lib.app.args.max_iters): prefix = 'iter' + str(iteration) + '_' - + if iteration == 0: RF_in_path = 'init_RF.txt' mask_in_path = 'mask.mif' @@ -112,6 +112,5 @@ def execute(): printMessage('Exiting after maximum ' + str(lib.app.args.max_iters) + ' iterations') shutil.copyfile('iter' + str(lib.app.args.max_iters-1) + '_RF.txt', 'response.txt') shutil.move('iter' + str(lib.app.args.max_iters-1) + '_SF.mif', 'voxels.mif') - - shutil.copyfile('response.txt', getUserPath(lib.app.args.output, False)) + shutil.copyfile('response.txt', getUserPath(lib.app.args.output, False)) From 37af63b64a8314d7f3b03f81b1fd05e70fc9a9a7 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Mon, 3 Oct 2016 21:08:33 +1100 Subject: [PATCH 178/723] Few more tweaks to mtbin script. Added scale factor output to mtnormalise --- cmd/mtnormalise.cpp | 23 +++++++++----- docs/reference/scripts/mtbin.rst | 7 ++++- scripts/mtbin | 51 ++++++++++++++++++-------------- 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/cmd/mtnormalise.cpp b/cmd/mtnormalise.cpp index 687868c042..d8cb31d4fb 100644 --- a/cmd/mtnormalise.cpp +++ b/cmd/mtnormalise.cpp @@ -55,7 +55,8 @@ void run () throw Exception ("The number of input arguments must be even. There must be an output file provided for every input tissue image"); std::vector> input_images; - std::vector> output_images; + std::vector
output_headers; + std::vector output_filenames; std::vector sh_image_indexes; for (size_t i = 0; i < argument.size(); i += 2) { @@ -64,13 +65,15 @@ void run () auto dc = Adapter::make (header.get_image(), 3, std::vector (1, 0)); input_images.emplace_back (Image::scratch(dc)); threaded_copy_with_progress_message ("loading image", dc, input_images[i / 2]); - sh_image_indexes.push_back(i / 2); + sh_image_indexes.push_back (i / 2); } else { input_images.emplace_back (Image::open(argument[i])); } if (i) check_dimensions (input_images[0], input_images[i / 2], 0, 3); - output_images.emplace_back (Image::create (argument[i + 1], header)); + // we can't create the image yet if we want to put the scale factor into the output header + output_filenames.push_back (argument[i + 1]); + output_headers.emplace_back (header); } Image mask; @@ -114,13 +117,19 @@ void run () Eigen::MatrixXf w = X.jacobiSvd (Eigen::ComputeThinU | Eigen::ComputeThinV).solve(y); progress++; for (size_t j = 0; j < input_images.size(); ++j) { + float scale_factor = w(j,0); + // If scale factor already present, we accumulate (for use in mtbin script) + if (input_images[j].keyval().count("normalisation_scale_factor")) + scale_factor *= std::stof(input_images[j].keyval().at("normalisation_scale_factor")); + output_headers[j].keyval()["normalisation_scale_factor"] = str(scale_factor); + auto output_image = Image::create (output_filenames[j], output_headers[j]); if (std::find (sh_image_indexes.begin(), sh_image_indexes.end(), j) != sh_image_indexes.end()) { auto input = Image::open (argument[j * 2]); - for (auto i = Loop (input) (input, output_images[j]); i; ++i) - output_images[j].value() = input.value() * w(j,0); + for (auto i = Loop (input) (input, output_image); i; ++i) + output_image.value() = input.value() * w(j,0); } else { - for (auto i = Loop (input_images[j]) (input_images[j], output_images[j]); i; ++i) - output_images[j].value() = input_images[j].value() * w(j,0); + for (auto i = Loop (input_images[j]) (input_images[j], output_image); i; ++i) + output_image.value() = input_images[j].value() * w(j,0); } progress++; } diff --git a/docs/reference/scripts/mtbin.rst b/docs/reference/scripts/mtbin.rst index 91bb7b1785..e25df8c103 100644 --- a/docs/reference/scripts/mtbin.rst +++ b/docs/reference/scripts/mtbin.rst @@ -27,7 +27,7 @@ Options for the mtbin script - **-value** specify the value to which the summed tissue compartments will be globally normalised to (Default: sqrt(1/(4*pi) = 0.282) -- **-iter** bias field correction and intensity normalisation is performed iteratively. Specify the number of iterations (Default: 5) +- **-iter** bias field correction and intensity normalisation is performed iteratively. Specify the number of iterations (Default: 4) Standard options ^^^^^^^^^^^^^^^^ @@ -50,6 +50,11 @@ Standard options - **-debug** Display additional debugging information over and above the verbose output +optional arguments +^^^^^^^^^^^^^^^^^^ + +- **-bias** Output estimated bias field + -------------- diff --git a/scripts/mtbin b/scripts/mtbin index aac952f056..ca5fd7d689 100755 --- a/scripts/mtbin +++ b/scripts/mtbin @@ -1,6 +1,6 @@ #!/usr/bin/env python -import math, os, sys +import math, os, sys, glob import lib.app, lib.cmdlineParser def abspath(*arg): @@ -23,7 +23,7 @@ lib.app.parser.add_argument('input_output', nargs='*', help='list of all input a 'Note that any number of tissues can be normalised') options = lib.app.parser.add_argument_group('Options for the mtbin script') options.add_argument('-mask', help='define the mask to compute the normalisation within. If not supplied this is estimated automatically') -options.add_argument('-value', help='specify the value to which the summed tissue compartments will be globally normalised to (Default: sqrt(1/(4*pi) = 0.282)') +options.add_argument('-value', default='0.282094', help='specify the value to which the summed tissue compartments will be globally normalised to (Default: sqrt(1/(4*pi) = 0.282)') options.add_argument('-iter', default='4', help='bias field correction and intensity normalisation is performed iteratively. Specify the number of iterations (Default: 4)') lib.app.parser.add_argument('-bias', help='Output estimated bias field') @@ -32,17 +32,13 @@ lib.app.initialise() masking = '' if lib.app.args.mask: - masking = '-mask ' + relpath(lib.app.args.mask_dir) + masking = ' -mask ' + relpath(lib.app.args.mask_dir) + ' ' -max_iterations = 4 -if lib.app.args.iter: - normalise_value = int(lib.app.args.iter) +max_iterations = int(lib.app.args.iter) if (len(lib.app.args.input_output) % 2): errorMessage ("The number of input arguments must be even. There must be an output file provided for every input tissue image") -lib.app.makeTempDir() - input_files = [] output_files = [] for i in range(0, len(lib.app.args.input_output)): @@ -50,16 +46,23 @@ for i in range(0, len(lib.app.args.input_output)): lib.app.checkOutputFile(lib.app.args.input_output[i]) output_files.append(lib.app.args.input_output[i]) else: - input_files.append(os.path.basename(lib.app.args.input_output[i])) - runCommand('cp -r ' + lib.app.args.input_output[i] + ' ' + lib.app.tempDir) + input_files.append(os.path.join(lib.app.workingDir, lib.app.args.input_output[i])) + +lib.app.makeTempDir() lib.app.gotoTempDir() -files_to_normalise = input_files +norm_files = ['norm_' + os.path.basename(s) for s in input_files] +bias_corrected_files = ['bias_' + os.path.basename(s) for s in input_files] for iteration in range(1, max_iterations + 1): printMessage('iteration: ' + str(iteration)) - norm_files = ['norm_' + s for s in input_files] - runCommand('mtnormalise -force ' + ' '.join([x for t in zip(files_to_normalise, norm_files) for x in t])) + + # normalise to 1 while estimating the bias field for output + if iteration == 1: + runCommand('mtnormalise -force -value 1.0 ' + masking + ' '.join([x for t in zip(input_files, norm_files) for x in t])) + else: + runCommand('mtnormalise -force -value 1.0 ' + masking + ' '.join([x for t in zip(bias_corrected_files, norm_files) for x in t])) + files_to_sum = [] for f in norm_files: sizes = getHeaderInfo(f, 'size').split() @@ -70,19 +73,21 @@ for iteration in range(1, max_iterations + 1): else: files_to_sum.append(f) runCommand('mrmath ' + ' '.join(files_to_sum) + ' sum summed.mif -force') - runCommand('mrmodelfield summed.mif field.mif -force') - - if iteration < max_iterations: - files_to_bias_correct = input_files - else: - files_to_bias_correct = norm_files + runCommand('mrmodelfield summed.mif field' + str(iteration) + '.mif -force' + masking) - bias_corrected_files = ['bias_' + s for s in input_files] - for a, b in zip(files_to_bias_correct, bias_corrected_files): - runCommand('mrcalc -force ' + a + ' field.mif -divide ' + b) + for a, b in zip(norm_files, bias_corrected_files): + runCommand('mrcalc -force ' + a + ' field' + str(iteration) + '.mif -divide ' + b) files_to_normalise = bias_corrected_files -for a, b in zip(bias_corrected_files, output_files): +# Final normalisation to value desired by user +runCommand('mtnormalise -force -value ' + lib.app.args.value + ' ' + masking + ' '.join([x for t in zip(bias_corrected_files, norm_files) for x in t])) + +for a, b in zip(norm_files, output_files): runCommand('mrconvert ' + a + ' ' + getUserPath(b, True) + lib.app.mrtrixForce) +# combine all fields from each iteration for output +if lib.app.args.bias: + runCommand('mrmath ' + ' '.join(glob.glob('field*.mif')) + ' product total_field.mif') + runCommand('mrconvert total_field.mif ' + getUserPath(lib.app.args.bias, True) + lib.app.mrtrixForce) + lib.app.complete() From 85c542b72d0043572ce8e8517933eda1351f9ff2 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 6 Oct 2016 13:31:32 +1100 Subject: [PATCH 179/723] dwipreproc: Change -topup_images to -se_epi Also update documentation to reflect new dwipreproc interface --- docs/tutorials/basic_dwi_processing.rst | 7 ++--- ...reprocessing_for_quantitative_analysis.rst | 2 +- scripts/dwipreproc | 26 +++++++++---------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/docs/tutorials/basic_dwi_processing.rst b/docs/tutorials/basic_dwi_processing.rst index 6f452071a0..a264174c0f 100644 --- a/docs/tutorials/basic_dwi_processing.rst +++ b/docs/tutorials/basic_dwi_processing.rst @@ -22,12 +22,13 @@ current-induced distortions and inter-volume subject motion. Procedures for this correct are not yet implemented in *MRtrix3*, though we do provide a script for interfacing with the relevant FSL tools: -``dwipreproc [options]`` +``dwipreproc [options]`` For more details, see the :ref:`dwipreproc` help file. In particular, it is necessary to manually specify what type of reversed -phase-encoding acquisition has taken place (if any), and provide the -relevant input images. +phase-encoding acquisition has taken place (if any), and potentially +provide additional relevant input images or provide details of the +phase encoding scheme used in the acquisition. DWI brain mask estimation ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/workflows/DWI_preprocessing_for_quantitative_analysis.rst b/docs/workflows/DWI_preprocessing_for_quantitative_analysis.rst index e128828ea8..fec3a00461 100644 --- a/docs/workflows/DWI_preprocessing_for_quantitative_analysis.rst +++ b/docs/workflows/DWI_preprocessing_for_quantitative_analysis.rst @@ -40,7 +40,7 @@ The :code:`dwipreproc` script is provided for performing general pre-processing Usage of this script varies depending on the specific nature of the DWI acquisition with respect to EPI phase encoding - full details are available within the :ref:`dwipreproc` help file. Here, only a simple example is provided, where a single DWI series is acquired where all volumes have an anterior-posterior (A>>P) phase encoding direction:: - dwipreproc AP -rpe_none + dwipreproc -pe_dir AP -rpe_none 3. Estimate a brain mask diff --git a/scripts/dwipreproc b/scripts/dwipreproc index dd38fdc8f3..7231b2ed3f 100755 --- a/scripts/dwipreproc +++ b/scripts/dwipreproc @@ -9,7 +9,7 @@ # If however such information is not present in the image headers, then it is also possible for the user to manually specify the relevant information regarding phase encoding. This involves the following information: # * The fundamental acquisition protocol design regarding phase encoding. There are three common acquisition designs that are supported: # 1. All DWI volumes acquired using the same phase encode parameters, and no additional volumes acquired for the purpose of estimating the inhomogeneity field. In this case, eddy will only perform motion and eddy current distortion correction. This configuration is specified using the -rpe_none option. -# 2. All DWI volumes acquired using the same phase encode parameters; but for the purpose of estimating the inhomogeneity field (and subsequently correcting the resulting distortions in the DWIs), an additional pair (or multiple pairs) of image volumes are acquired, where the first volume(s) has the same phase encoding parameters as the input DWI series, and the second volume(s) has precisely the opposite phase encoding. This configuration is specified using the -rpe_pair option; and the user must additionally provide those images to be used for field estimation using the -topup_images option. +# 2. All DWI volumes acquired using the same phase encode parameters; but for the purpose of estimating the inhomogeneity field (and subsequently correcting the resulting distortions in the DWIs), an additional pair (or multiple pairs) of image volumes are acquired, where the first volume(s) has the same phase encoding parameters as the input DWI series, and the second volume(s) has precisely the opposite phase encoding. This configuration is specified using the -rpe_pair option; and the user must additionally provide those images to be used for field estimation using the -se_epi option. # 3. Every DWI gradient direction is acquired twice: once with one phase encoding configuration, and again using the opposite phase encode direction. The goal here is to combine each pair of images into a single DWI volume per gradient direction, where that recombination takes advantage of the information gained from having two volumes where the signal is distorted in opposite directions in the presence of field inhomogeneity. # * The (primary) direction of phase encoding. In cases where opposing phase encoding is part of the acquisition protocol (i.e. the reversed phase-encode pair in case 2 above, and all of the DWIs in case 3 above), the -pe_dir option specifies the phase encode direction of the _first_ volume in the relevant volume pair; the second is assumed to be the exact opposite. # * The total readout time of the EPI acquisition. This affects the magnitude of the image distortion for a given field inhomogeneity. If this information is not provided via the -readout_time option, then a 'sane' default of 0.1s will be assumed. Note that this is not actually expected to influence the estimation of the field; it will result in the field inhomogeneity estimation being scaled by some factor, but as long as it uses the same sane default for the DWIs, the distortion correction should operate as expected. @@ -52,16 +52,16 @@ lib.cmdlineParser.flagMutuallyExclusiveOptions( [ 'grad', 'fslgrad' ] ) options = lib.app.parser.add_argument_group('Other options for the dwipreproc script') options.add_argument('-pe_dir', metavar=('PE'), help='Manually specify the phase encoding direction of the input series; can be a signed axis number (e.g. -0, 1, +2), an axis designator (e.g. RL, PA, IS), or NIfTI axis codes (e.g. i-, j, k)') options.add_argument('-readout_time', metavar=('time'), type=float, help='Manually specify the total readout time of the input series (in seconds)') -options.add_argument('-topup_images', metavar=('file'), help='Provide an additional image series that is to be used exclusively by topup for estimating the inhomogeneity field (i.e. it will not form part of the output image series)') +options.add_argument('-se_epi', metavar=('file'), help='Provide an additional image series consisting of spin-echo EPI images, which is to be used exclusively by topup for estimating the inhomogeneity field (i.e. it will not form part of the output image series)') options.add_argument('-json_import', metavar=('JSON_file'), help='Import image header information from an associated JSON file (may be necessary to determine phase encoding information)') options.add_argument('-cuda', help='Use the CUDA version of eddy (if available)', action='store_true', default=False) rpe_options = lib.app.parser.add_argument_group('Options for specifying the acquisition phase-encoding design; note that one of the -rpe_* options MUST be provided') rpe_options.add_argument('-rpe_none', action='store_true', help='Specify that no reversed phase-encoding image data is being provided; eddy will perform eddy current and motion correction only') -rpe_options.add_argument('-rpe_pair', action='store_true', help='Specify that a set of images will be provided for use in inhomogeneity field estimation (using the -topup_images option), where it is assumed that the FIRST volume(s) of this image has the same phase-encoding direction as the input DWIs, and the LAST volume(s) has precisely the OPPOSITE phase encoding') +rpe_options.add_argument('-rpe_pair', action='store_true', help='Specify that a set of images will be provided for use in inhomogeneity field estimation (using the -se_epi option), where it is assumed that the FIRST volume(s) of this image has the same phase-encoding direction as the input DWIs, and the LAST volume(s) has precisely the OPPOSITE phase encoding') rpe_options.add_argument('-rpe_all', action='store_true', help='Specify that all DWIs have been acquired with opposing phase-encoding, where it is assumed that the second half of the volumes in the input DWIs have corresponding diffusion sensitisation directions to the first half, but were acquired using the opposite phase-encoding direction') rpe_options.add_argument('-rpe_header', action='store_true', help='Specify that the phase-encoding information can be found in the image header(s), and that this is the information that the script should use') lib.cmdlineParser.flagMutuallyExclusiveOptions( [ 'rpe_none', 'rpe_pair', 'rpe_all', 'rpe_header' ], True ) -lib.cmdlineParser.flagMutuallyExclusiveOptions( [ 'rpe_none', 'topup_images' ], False ) # May still technically provide -topup_images even with -rpe_all +lib.cmdlineParser.flagMutuallyExclusiveOptions( [ 'rpe_none', 'se_epi' ], False ) # May still technically provide -se_epi even with -rpe_all lib.cmdlineParser.flagMutuallyExclusiveOptions( [ 'rpe_header', 'pe_dir' ], False ) # Can't manually provide phase-encoding direction if expecting it to be in the header lib.cmdlineParser.flagMutuallyExclusiveOptions( [ 'rpe_header', 'readout_time' ], False ) # Can't manually provide readout time if expecting it to be in the header lib.app.initialise() @@ -75,8 +75,8 @@ if lib.app.args.rpe_none: PE_design = 'None' elif lib.app.args.rpe_pair: PE_design = 'Pair' - if not lib.app.args.topup_images: - errorMessage('If using the -rpe_pair option, the -topup_images option must be used to provide the image data to be used by topup') + if not lib.app.args.se_epi: + errorMessage('If using the -rpe_pair option, the -se_epi option must be used to provide the spin-echo EPI data to be used by topup') elif lib.app.args.rpe_all: PE_design = 'All' elif lib.app.args.rpe_header: @@ -133,8 +133,8 @@ json_option = '' if lib.app.args.json_import: json_option = ' -json_import ' + getUserPath(lib.app.args.json_import, True) runCommand('mrconvert ' + getUserPath(lib.app.args.input, True) + ' ' + os.path.join(lib.app.tempDir, 'dwi.mif') + grad_option + json_option) -if lib.app.args.topup_images: - runCommand('mrconvert ' + getUserPath(lib.app.args.topup_images, True) + ' ' + os.path.join(lib.app.tempDir, 'topup_in.mif')) +if lib.app.args.se_epi: + runCommand('mrconvert ' + getUserPath(lib.app.args.se_epi, True) + ' ' + os.path.join(lib.app.tempDir, 'topup_in.mif')) lib.app.gotoTempDir() @@ -143,10 +143,10 @@ lib.app.gotoTempDir() # Get information on the input images, particularly so that their validity can be checked dwi_size = [ int(s) for s in getHeaderInfo('dwi.mif', 'size').split() ] dwi_pe_scheme = getPEScheme('dwi.mif') -if lib.app.args.topup_images: +if lib.app.args.se_epi: topup_size = [ int(s) for s in getHeaderInfo('topup_in.mif', 'size').split() ] if not len(topup_size) == 4: - errorMessage('File provided using -topup_images option must contain more than one image volume') + errorMessage('File provided using -se_epi option must contain more than one image volume') topup_pe_scheme = getPEScheme('topup_in.mif') grad = getHeaderInfo('dwi.mif', 'dwgrad').split('\n') grad = [ line.split() for line in grad ] @@ -200,7 +200,7 @@ if manual_pe_dir: line.append(trt) dwi_manual_pe_scheme = [ line ] * num_volumes - # With 'Pair', also need to construct the manual scheme for topup_images + # With 'Pair', also need to construct the manual scheme for SE EPIs elif PE_design == 'Pair': line = manual_pe_dir.copy() line.append(trt) @@ -314,7 +314,7 @@ if topup_manual_pe_scheme: # Deal with the phase-encoding of the images to be fed to topup (if applicable) -if lib.app.args.topup_images: +if lib.app.args.se_epi: # 3 possible sources of PE information: DWI header, topup image header, command-line # Any pair of these may conflict, and any one could be absent @@ -325,7 +325,7 @@ if lib.app.args.topup_images: if PE_design == 'Pair': num_topup_volumes = topup_size[3] if num_topup_volumes%2: - errorMessage('If using -rpe_pair option, image provided using -topup_images must contain an even number of volumes'); + errorMessage('If using -rpe_pair option, image provided using -se_epi must contain an even number of volumes'); # Criteria: # * If present in own header, ignore DWI header entirely - # - If also provided at command-line, look for conflict & report From 3f78c911068a9c92f6d5d8121c69299b41d2ac7f Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 6 Oct 2016 16:47:31 +1100 Subject: [PATCH 180/723] Updates for testing in preparation for 0.3.16 - Fixed -transform real2first option in meshconvert. Fixed testing_diff_mesh to compile against src/surface/ changes. Removed data and tests for removed functionalities. --- src/surface/filter/vertex_transform.cpp | 1 + testing/cmd/testing_diff_mesh.cpp | 13 +++++++------ testing/data | 2 +- testing/tests/mrstats | 1 - testing/tests/mrthreshold | 1 - 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/surface/filter/vertex_transform.cpp b/src/surface/filter/vertex_transform.cpp index 834fd2bcd9..afd845e91a 100644 --- a/src/surface/filter/vertex_transform.cpp +++ b/src/surface/filter/vertex_transform.cpp @@ -60,6 +60,7 @@ namespace MR Vertex v = in.vert(i); v = transform.scanner2image * v; v[0] = ((header.size(0)-1) * header.spacing(0)) - v[0]; + vertices.push_back (std::move (v)); } if (in.have_normals()) { for (size_t i = 0; i != V; ++i) { diff --git a/testing/cmd/testing_diff_mesh.cpp b/testing/cmd/testing_diff_mesh.cpp index e9a08b4bcd..7e0d049a2a 100644 --- a/testing/cmd/testing_diff_mesh.cpp +++ b/testing/cmd/testing_diff_mesh.cpp @@ -24,7 +24,8 @@ #include "progressbar.h" #include "types.h" -#include "mesh/mesh.h" +#include "surface/mesh.h" +#include "surface/mesh_multi.h" using namespace MR; using namespace App; @@ -49,18 +50,18 @@ void run () { const default_type dist_sq = Math::pow2 (default_type(argument[2])); - MR::Mesh::MeshMulti multi_in1, multi_in2; + MR::Surface::MeshMulti multi_in1, multi_in2; // Read in the mesh data try { - MR::Mesh::Mesh mesh (argument[0]); + MR::Surface::Mesh mesh (argument[0]); multi_in1.push_back (mesh); } catch (...) { multi_in1.load (argument[0]); } try { - MR::Mesh::Mesh mesh (argument[1]); + MR::Surface::Mesh mesh (argument[1]); multi_in2.push_back (mesh); } catch (...) { multi_in2.load (argument[1]); @@ -71,8 +72,8 @@ void run () for (size_t mesh_index = 0; mesh_index != multi_in1.size(); ++mesh_index) { - const MR::Mesh::Mesh& in1 (multi_in1[mesh_index]); - const MR::Mesh::Mesh& in2 (multi_in2[mesh_index]); + const MR::Surface::Mesh& in1 (multi_in1[mesh_index]); + const MR::Surface::Mesh& in2 (multi_in2[mesh_index]); // Can't test this: Some formats have to duplicate the vertex positions //if (in1.num_vertices() != in2.num_vertices()) diff --git a/testing/data b/testing/data index b6d0e018d0..897813cba6 160000 --- a/testing/data +++ b/testing/data @@ -1 +1 @@ -Subproject commit b6d0e018d0bad96edec423b81ef1b006a94932fb +Subproject commit 897813cba6f2c2c26a2fe8745c7324aa4397150b diff --git a/testing/tests/mrstats b/testing/tests/mrstats index d0fcc81604..5b761aa325 100644 --- a/testing/tests/mrstats +++ b/testing/tests/mrstats @@ -1,3 +1,2 @@ mrstats dwi.mif -output mean -output median -output std -output min -output max -output count > tmp.txt && testing_diff_matrix tmp.txt mrstats/out.txt -frac 1e-5 mrstats dwi.mif -output mean -output median -output std -output min -output max -output count -mask mask.mif > tmp.txt && testing_diff_matrix tmp.txt mrstats/masked.txt -frac 1e-5 -mrstats dwi.mif -histogram tmp.txt -force && testing_diff_matrix tmp.txt mrstats/hist.txt diff --git a/testing/tests/mrthreshold b/testing/tests/mrthreshold index 39264879f7..f8ba1d4f60 100644 --- a/testing/tests/mrthreshold +++ b/testing/tests/mrthreshold @@ -1,7 +1,6 @@ mrthreshold dwi_mean.mif - | testing_diff_data - mrthreshold/default.mif mrthreshold dwi_mean.mif - -invert | testing_diff_data - mrthreshold/invert.mif mrthreshold dwi_mean.mif - -abs 100 | testing_diff_data - mrthreshold/abs100.mif -mrthreshold dwi_mean.mif - -histogram | testing_diff_data - mrthreshold/hist.mif mrthreshold dwi_mean.mif - -percentile 50 | testing_diff_data - mrthreshold/perc50.mif mrthreshold dwi_mean.mif - -top 20 | testing_diff_data - mrthreshold/top20.mif mrthreshold dwi_mean.mif - -bottom 20 | testing_diff_data - mrthreshold/bottom20.mif From 1ecb21c2830b436b7352a993cea57a799515cbd7 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 6 Oct 2016 17:37:08 +1100 Subject: [PATCH 181/723] Header::realign_transform(): Also realign PE scheme --- lib/header.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/header.cpp b/lib/header.cpp index d3a8902996..957bab6754 100644 --- a/lib/header.cpp +++ b/lib/header.cpp @@ -15,6 +15,7 @@ #include "app.h" #include "header.h" +#include "phase_encoding.h" #include "stride.h" #include "transform.h" #include "image_io/default.h" @@ -487,6 +488,21 @@ namespace MR axes_[1] = a[1]; axes_[2] = a[2]; + // If there's any phase encoding direction information present in the + // header, it's necessary here to update it according to the + // flips / permutations that have taken place + auto pe_scheme = PhaseEncoding::get_scheme (*this); + if (!pe_scheme.rows()) return; + for (ssize_t row = 0; row != pe_scheme.rows(); ++row) { + Eigen::VectorXd new_line (pe_scheme.row (row)); + for (ssize_t axis = 0; axis != 3; ++axis) { + new_line[axis] = pe_scheme(row, perm[axis]); + if (new_line[axis] && flip[axis]) + new_line[axis] = -new_line[axis]; + } + pe_scheme.row (row) = new_line; + } + PhaseEncoding::set_scheme (*this, pe_scheme); } From ef836051995e65b1c675d13abcec025ec243e323 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 3 Oct 2016 12:41:45 +1100 Subject: [PATCH 182/723] ProgressBar: Clear line on Windows This is more appropriate following d8a0d1f and edcd61f: MSYS2 supports VT100 codes, and redirection to file is handled explicitly, so therefore line clear codes can be safely and correctly handled on Windows. --- lib/progressbar.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/progressbar.cpp b/lib/progressbar.cpp index 1c746cc028..afaece7b68 100644 --- a/lib/progressbar.cpp +++ b/lib/progressbar.cpp @@ -16,11 +16,8 @@ #include "app.h" #include "progressbar.h" -#ifdef MRTRIX_WINDOWS -# define CLEAR_LINE_CODE -#else -# define CLEAR_LINE_CODE "\033[0K" -#endif +// MSYS2 supports VT100, and file redirection is handled explicitly so this can be used globally +#define CLEAR_LINE_CODE "\033[0K" namespace MR { From 44f40f308c27868a741c990502af24b826b5f9d9 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 3 Oct 2016 12:42:53 +1100 Subject: [PATCH 183/723] tck2connectome: Tweaks for -vector option operation --- src/dwi/tractography/connectome/matrix.cpp | 36 ++++++++++++++++------ src/dwi/tractography/connectome/matrix.h | 1 + 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/dwi/tractography/connectome/matrix.cpp b/src/dwi/tractography/connectome/matrix.cpp index aae2c277ee..67d0087cb7 100644 --- a/src/dwi/tractography/connectome/matrix.cpp +++ b/src/dwi/tractography/connectome/matrix.cpp @@ -45,23 +45,31 @@ bool Matrix::operator() (const Mapped_track_nodepair& in) assert (in.get_second_node() < data.rows()); assert (assignments_lists.empty()); if (is_vector()) { - apply (data (0, in.get_first_node()), in.get_factor(), in.get_weight()); - counts (0, in.get_first_node()) += in.get_weight(); + assert (assignments_pairs.empty()); apply (data (0, in.get_second_node()), in.get_factor(), in.get_weight()); counts (0, in.get_second_node()) += in.get_weight(); + if (in.get_track_index() == assignments_single.size()) { + assignments_single.push_back (in.get_second_node()); + } else if (in.get_track_index() < assignments_single.size()) { + assignments_single[in.get_track_index()] = in.get_second_node(); + } else { + assignments_single.resize (in.get_track_index() + 1, 0); + assignments_single[in.get_track_index()] = in.get_second_node(); + } } else { + assert (assignments_singles.empty()); const node_t row = std::min (in.get_first_node(), in.get_second_node()); const node_t column = std::max (in.get_first_node(), in.get_second_node()); apply (data (row, column), in.get_factor(), in.get_weight()); counts (row, column) += in.get_weight(); - } - if (in.get_track_index() == assignments_pairs.size()) { - assignments_pairs.push_back (in.get_nodes()); - } else if (in.get_track_index() < assignments_pairs.size()) { - assignments_pairs[in.get_track_index()] = in.get_nodes(); - } else { - assignments_pairs.resize (in.get_track_index() + 1, std::make_pair (0, 0)); - assignments_pairs[in.get_track_index()] = in.get_nodes(); + if (in.get_track_index() == assignments_pairs.size()) { + assignments_pairs.push_back (in.get_nodes()); + } else if (in.get_track_index() < assignments_pairs.size()) { + assignments_pairs[in.get_track_index()] = in.get_nodes(); + } else { + assignments_pairs.resize (in.get_track_index() + 1, std::make_pair (0, 0)); + assignments_pairs[in.get_track_index()] = in.get_nodes(); + } } return true; } @@ -70,6 +78,7 @@ bool Matrix::operator() (const Mapped_track_nodepair& in) bool Matrix::operator() (const Mapped_track_nodelist& in) { + assert (assignments_single.empty()); assert (assignments_pairs.empty()); std::vector list (in.get_nodes()); for (std::vector::const_iterator i = list.begin(); i != list.end(); ++i) { @@ -172,6 +181,11 @@ void Matrix::remove_unassigned() void Matrix::error_check (const std::set& missing_nodes) { + // Don't bother looking for empty nodes if we're generating a + // connectivity vector from a seed region rather than a + // connectome from a whole-brain tractogram + if (counts.rows() == 1) + return; std::vector node_counts (data.cols(), 0); for (node_t i = 0; i != counts.rows(); ++i) { for (node_t j = i; j != counts.cols(); ++j) { @@ -199,6 +213,8 @@ void Matrix::error_check (const std::set& missing_nodes) void Matrix::write_assignments (const std::string& path) const { File::OFStream stream (path); + for (auto i = assignments_single.begin(); i != assignments_single.end(); ++i) + stream << str(*i) << "\n"; for (auto i = assignments_pairs.begin(); i != assignments_pairs.end(); ++i) stream << str(i->first) << " " << str(i->second) << "\n"; for (auto i = assignments_lists.begin(); i != assignments_lists.end(); ++i) { diff --git a/src/dwi/tractography/connectome/matrix.h b/src/dwi/tractography/connectome/matrix.h index d592fda8bd..dd471451fc 100644 --- a/src/dwi/tractography/connectome/matrix.h +++ b/src/dwi/tractography/connectome/matrix.h @@ -75,6 +75,7 @@ class Matrix private: MR::Connectome::matrix_type data, counts; const stat_edge statistic; + std::vector assignments_single; std::vector assignments_pairs; std::vector< std::vector > assignments_lists; From 3686ef6d3ce7b711e032dd42ec459ef895d2c10e Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 3 Oct 2016 12:44:28 +1100 Subject: [PATCH 184/723] Scripts: Basic ProgressBar functionality --- scripts/lib/app.py | 2 ++ scripts/lib/progressBar.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 scripts/lib/progressBar.py diff --git a/scripts/lib/app.py b/scripts/lib/app.py index 9101f83e34..12039b6976 100644 --- a/scripts/lib/app.py +++ b/scripts/lib/app.py @@ -24,6 +24,7 @@ verbosity = 1 workingDir = '' +clearLine = '' colourClear = '' colourConsole = '' colourDebug = '' @@ -78,6 +79,7 @@ def initialise(): # Windows now also gets coloured text terminal support, so make this the default use_colour = True if use_colour: + clearLine = '\033[0K' colourClear = '\033[0m' colourConsole = '\033[03;36m' colourDebug = '\033[03;34m' diff --git a/scripts/lib/progressBar.py b/scripts/lib/progressBar.py new file mode 100644 index 0000000000..b915974354 --- /dev/null +++ b/scripts/lib/progressBar.py @@ -0,0 +1,29 @@ +import os, sys +import lib.app + + +class progressBar: + + def _update(self): + sys.stderr.write('\r' + lib.app.colourPrint + os.path.basename(sys.argv[0]) + ': ' + lib.app.colourClear + '[{0:>3}%] '.format(int(round(100.0*self.counter/self.target))) + self.message + '...' + lib.app.clearLine + self.newline) + + def __init__(self, msg, target): + self.counter = 0 + self.message = msg + self.newline = '\n' if lib.app.verbosity > 1 else '' # If any more than default verbosity, may still get details printed in between progress updates + self.orig_verbosity = lib.app.verbosity + self.target = target + lib.app.verbosity = lib.app.verbosity - 1 if lib.app.verbosity else 0 + self._update() + + def increment(self, msg=''): + if msg: + self.message = msg + self.counter += 1 + self._update() + + def done(self): + self.counter = target + sys.stderr.write('\r' + lib.app.colourPrint + os.path.basename(sys.argv[0]) + ': ' + lib.app.colourClear + '[100%] ' + self.message + lib.app.clearLine + '\n') + lib.app.verbosity = self.orig_verbosity + \ No newline at end of file From b7f6a3873ab4259478b35360028e827d7e1b1b14 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 7 Oct 2016 18:15:33 +1100 Subject: [PATCH 185/723] maskedit: Fix -voxel option for ndim>3 --- cmd/mredit.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/cmd/mredit.cpp b/cmd/mredit.cpp index 0636f7b713..9397cfb8fd 100644 --- a/cmd/mredit.cpp +++ b/cmd/mredit.cpp @@ -111,6 +111,8 @@ void run () 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"); size_t operation_count = 0; @@ -185,11 +187,23 @@ void run () 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"); - Eigen::Vector3 p (position[0], position[1], position[2]); - if (scanner) + if (scanner) { + Eigen::Vector3 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"); + 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"); + out.index(axis) = position[axis]; + } + } out.value() = value; } From bd549e9b9def8f530ec0ad8221378835a41522d7 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 7 Oct 2016 18:16:55 +1100 Subject: [PATCH 186/723] Registration: Thread safety in Demons class destructors --- src/dwi/tractography/connectome/matrix.cpp | 2 +- src/registration/metric/demons.h | 6 ++++++ src/registration/metric/demons4D.h | 6 ++++++ src/registration/nonlinear.h | 2 +- src/registration/transform/initialiser_helpers.h | 4 ++-- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/dwi/tractography/connectome/matrix.cpp b/src/dwi/tractography/connectome/matrix.cpp index 67d0087cb7..801978bf4f 100644 --- a/src/dwi/tractography/connectome/matrix.cpp +++ b/src/dwi/tractography/connectome/matrix.cpp @@ -57,7 +57,7 @@ bool Matrix::operator() (const Mapped_track_nodepair& in) assignments_single[in.get_track_index()] = in.get_second_node(); } } else { - assert (assignments_singles.empty()); + assert (assignments_single.empty()); const node_t row = std::min (in.get_first_node(), in.get_second_node()); const node_t column = std::max (in.get_first_node(), in.get_second_node()); apply (data (row, column), in.get_factor(), in.get_weight()); diff --git a/src/registration/metric/demons.h b/src/registration/metric/demons.h index 1aa2a2985d..de251c95ab 100644 --- a/src/registration/metric/demons.h +++ b/src/registration/metric/demons.h @@ -16,6 +16,9 @@ #ifndef __registration_metric_demons_h__ #define __registration_metric_demons_h__ +#include + +#include "image_helpers.h" #include "adapter/gradient3D.h" namespace MR @@ -34,6 +37,7 @@ namespace MR global_voxel_count (global_voxel_count), thread_cost (0.0), thread_voxel_count (0), + mutex (new std::mutex), normaliser (0.0), robustness_parameter (-1.e12), intensity_difference_threshold (0.001), @@ -47,6 +51,7 @@ namespace MR } ~Demons () { + std::lock_guard lock (*mutex); global_cost += thread_cost; global_voxel_count += thread_voxel_count; } @@ -123,6 +128,7 @@ namespace MR size_t& global_voxel_count; default_type thread_cost; size_t thread_voxel_count; + std::shared_ptr mutex; default_type normaliser; const default_type robustness_parameter; const default_type intensity_difference_threshold; diff --git a/src/registration/metric/demons4D.h b/src/registration/metric/demons4D.h index 64f20d5256..965bdcf6b5 100644 --- a/src/registration/metric/demons4D.h +++ b/src/registration/metric/demons4D.h @@ -16,6 +16,9 @@ #ifndef __registration_metric_demons4D_h__ #define __registration_metric_demons4D_h__ +#include + +#include "image_helpers.h" #include "adapter/gradient3D.h" namespace MR @@ -34,6 +37,7 @@ namespace MR global_voxel_count (global_voxel_count), thread_cost (0.0), thread_voxel_count (0), + mutex (new std::mutex), normaliser (0.0), robustness_parameter (-1.e12), intensity_difference_threshold (0.001), @@ -47,6 +51,7 @@ namespace MR } ~Demons4D () { + std::lock_guard lock (*mutex); global_cost += thread_cost; global_voxel_count += thread_voxel_count; } @@ -132,6 +137,7 @@ namespace MR size_t& global_voxel_count; default_type thread_cost; size_t thread_voxel_count; + std::shared_ptr mutex; default_type normaliser; const default_type robustness_parameter; const default_type intensity_difference_threshold; diff --git a/src/registration/nonlinear.h b/src/registration/nonlinear.h index d38c79fb13..52ba91e525 100644 --- a/src/registration/nonlinear.h +++ b/src/registration/nonlinear.h @@ -197,7 +197,7 @@ namespace MR Image im2_deform_field = Image::scratch (field_header); if (iteration > 1) { - DEBUG ("updating displacement field field"); + DEBUG ("updating displacement field"); Warp::update_displacement_scaling_and_squaring (*im1_to_mid, *im1_update, *im1_to_mid_new, grad_step_altered); Warp::update_displacement_scaling_and_squaring (*im2_to_mid, *im2_update, *im2_to_mid_new, grad_step_altered); diff --git a/src/registration/transform/initialiser_helpers.h b/src/registration/transform/initialiser_helpers.h index a610dcba2c..76df064bed 100644 --- a/src/registration/transform/initialiser_helpers.h +++ b/src/registration/transform/initialiser_helpers.h @@ -88,7 +88,7 @@ namespace MR sh1.setZero(); sh2.resize(N); sh2.setZero(); - }; + } void run (); @@ -122,7 +122,7 @@ namespace MR transform(transform), mask1(mask1), mask2(mask2), - use_mask_values_instead (mask_is_intensity) {}; + use_mask_values_instead (mask_is_intensity) {} void run (); protected: From b01603f05ab69fb93dbacb3853971d5a8b61dd1f Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Mon, 10 Oct 2016 14:03:02 +1100 Subject: [PATCH 187/723] Fix to mrmodelfield to use different matrix solve --- cmd/mrmodelfield.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/mrmodelfield.cpp b/cmd/mrmodelfield.cpp index 9f09c2bfe3..2f2bfed377 100644 --- a/cmd/mrmodelfield.cpp +++ b/cmd/mrmodelfield.cpp @@ -95,6 +95,7 @@ void run () threshold_filter (input, mask); } + size_t num_voxels = 0; for (auto i = Loop (mask) (mask); i; ++i) { if (mask.value()) @@ -105,7 +106,6 @@ void run () Eigen::VectorXf y (num_voxels); y.setOnes(); - { ProgressBar progress ("fitting model..."); size_t counter = 0; @@ -120,7 +120,7 @@ void run () } progress++; - Eigen::VectorXf w = X.jacobiSvd (Eigen::ComputeThinU | Eigen::ComputeThinV).solve(y); + Eigen::VectorXf w = X.colPivHouseholderQr().solve(y); progress++; for (auto i = Loop (output) (output); i; ++i) { From 66723a408c6f782636681f8cc8e645a196123812 Mon Sep 17 00:00:00 2001 From: rtabbara Date: Mon, 10 Oct 2016 16:48:37 +1100 Subject: [PATCH 188/723] Vector plot: Correctly compute thresholding scaling relative to colour data value * This threshold scaling relative to colour value is displayed within the colourbar * Clamp the range to be the intersection of the windowing --- src/gui/mrview/tool/vector/vector_structs.h | 24 +++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/gui/mrview/tool/vector/vector_structs.h b/src/gui/mrview/tool/vector/vector_structs.h index de779870a2..4a7426257b 100644 --- a/src/gui/mrview/tool/vector/vector_structs.h +++ b/src/gui/mrview/tool/vector/vector_structs.h @@ -57,16 +57,32 @@ namespace MR float get_relative_threshold_lower (FixelValue& fixel_value) const { - float factor = (lessthan - value_min)/(value_max - value_min); - return (1 - factor) * fixel_value.value_min + factor * fixel_value.value_max; + float relative_min = std::numeric_limits::max(); + + // Find min value based relative to lower-thresholded fixels + for(size_t i = 0, N = buffer_store.size(); i < N; ++i) { + if (buffer_store[i] > lessthan) + relative_min = std::min(relative_min, fixel_value.buffer_store[i]); + } + + // Clamp our value to windowing + return std::max(relative_min, fixel_value.current_min); } float get_relative_threshold_upper (FixelValue& fixel_value) const { - float factor = (greaterthan - value_min)/(value_max - value_min); - return (1 - factor) * fixel_value.value_min + factor * fixel_value.value_max; + float relative_max = std::numeric_limits::min(); + + // Find max value based relative to upper-thresholded fixels + for(size_t i = 0, N = buffer_store.size(); i < N; ++i) { + if (buffer_store[i] < greaterthan) + relative_max = std::max(relative_max, fixel_value.buffer_store[i]); + } + + // Clamp our value to windowing + return std::min(relative_max, fixel_value.current_max); } }; From f4e891ea3e1d42147dab0bc882da72c41f5083d7 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Mon, 10 Oct 2016 16:50:10 +1100 Subject: [PATCH 189/723] added tck2fixel as a temporary method for computing fixel TDI maps. --- cmd/fixelcfestats.cpp | 2 - cmd/tck2fixel.cpp | 187 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 cmd/tck2fixel.cpp diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 39f1fa9f4d..f76e6d6500 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -29,7 +29,6 @@ #include "stats/enhance.h" #include "stats/permtest.h" #include "dwi/tractography/file.h" -#include "dwi/tractography/scalar_file.h" #include "dwi/tractography/mapping/mapper.h" #include "dwi/tractography/mapping/loader.h" #include "dwi/tractography/mapping/writer.h" @@ -123,7 +122,6 @@ void write_fixel_output (const std::string& filename, const VectorType& data, const Header& header) { - assert (data.size() == header.size(0)); auto output = Image::create (filename, header); for (uint32_t i = 0; i < data.size(); ++i) { output.index(0) = i; diff --git a/cmd/tck2fixel.cpp b/cmd/tck2fixel.cpp new file mode 100644 index 0000000000..57730506bf --- /dev/null +++ b/cmd/tck2fixel.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + +#include "command.h" +#include "progressbar.h" +#include "algo/loop.h" +#include "image.h" + +#include "fixel_format/helpers.h" +#include "fixel_format/keys.h" +#include "fixel_format/loop.h" + +#include "dwi/tractography/mapping/mapper.h" +#include "dwi/tractography/mapping/loader.h" +#include "dwi/tractography/mapping/writer.h" + + +using namespace MR; +using namespace App; + +#define DEFAULT_ANGLE_THRESHOLD 45.0 + + +class TrackProcessor { + + public: + + typedef DWI::Tractography::Mapping::SetVoxelDir SetVoxelDir; + + TrackProcessor (Image& fixel_indexer, + const std::vector& fixel_directions, + std::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 + std::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; + uint32_t num_fibres = fixel_indexer.value(); + if (num_fibres > 0) { + fixel_indexer.index(3) = 1; + uint32_t first_index = fixel_indexer.value(); + uint32_t last_index = first_index + num_fibres; + uint32_t closest_fixel_index = 0; + float largest_dp = 0.0; + const Eigen::Vector3 dir (i->get_dir().normalized()); + for (uint32_t j = first_index; j < last_index; ++j) { + const float dp = std::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 std::vector& fixel_directions; + std::vector& fixel_TDI; + const float angular_threshold_dp; +}; + + +void usage () +{ + AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; + + DESCRIPTION + + "compute a fixel TDI map from a tractogram"; + + 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_text() + + Argument ("fixel_folder_out", "the output fixel folder. 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_image_out(); + + 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); +} + + + +template +void write_fixel_output (const std::string& filename, + const VectorType& data, + const Header& header) +{ + auto output = Image::create (filename, header); + for (uint32_t i = 0; i < data.size(); ++i) { + output.index(0) = i; + output.value() = data[i]; + } +} + + + +void run () +{ + const std::string input_fixel_folder = argument[1]; + Header index_header = FixelFormat::find_index_header (input_fixel_folder); + auto index_image = index_header.get_image(); + + const uint32_t num_fixels = FixelFormat::get_number_of_fixels (index_header); + + const float angular_threshold = get_option_value ("angle", DEFAULT_ANGLE_THRESHOLD); + + std::vector positions (num_fixels); + std::vector directions (num_fixels); + + const std::string output_fixel_folder = argument[2]; + FixelFormat::copy_index_and_directions_file (input_fixel_folder, output_fixel_folder); + + { + auto directions_data = FixelFormat::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::Vector3 vox ((default_type)index_image.index(0), (default_type)index_image.index(1), (default_type)index_image.index(2)); + index_image.index(3) = 1; + uint32_t offset = index_image.value(); + size_t fixel_index = 0; + for (auto f = FixelFormat::FixelLoop (index_image) (directions_data); f; ++f, ++fixel_index) { + directions[offset + fixel_index] = directions_data.row(1); + positions[offset + fixel_index] = image_transform.voxel2scanner * vox; + } + } + } + + std::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); + // Read in tracts, and compute whole-brain fixel-fixel connectivity + const size_t num_tracks = properties["count"].empty() ? 0 : to (properties["count"]); + if (!num_tracks) + throw Exception ("no tracks found in input file"); + + { + typedef DWI::Tractography::Mapping::SetVoxelDir 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); + } + track_file.close(); + + Header output_header (FixelFormat::data_header_from_index (index_image)); + + write_fixel_output (Path::join (output_fixel_folder, argument[3]), fixel_TDI, output_header); + +} From 5419e44ae29b9f8642e2c0c2e300cb7de08c7853 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 17 Oct 2016 16:15:07 +1100 Subject: [PATCH 190/723] JSON export: Rotate phase encoding scheme As discussed in #747. --- lib/file/json_utils.cpp | 27 +++++++++++++++++++++++++-- lib/header.cpp | 20 +++++++++++--------- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/lib/file/json_utils.cpp b/lib/file/json_utils.cpp index 0cc5d94576..210385a39f 100644 --- a/lib/file/json_utils.cpp +++ b/lib/file/json_utils.cpp @@ -14,12 +14,15 @@ */ #include +#include #include "file/json_utils.h" +#include "file/nifti_utils.h" #include "exception.h" #include "header.h" #include "mrtrix.h" +#include "phase_encoding.h" #include "file/ofstream.h" namespace MR @@ -68,8 +71,28 @@ namespace MR void save (const Header& H, const std::string& path) { nlohmann::json json; - for (const auto& kv : H.keyval()) - json[kv.first] = kv.second; + auto pe_scheme = PhaseEncoding::get_scheme (H); + std::vector order; + File::NIfTI::adjust_transform (H, order); + if (pe_scheme.rows() && (order[0] != 0 || order[1] != 1 || order[2] != 2 || H.stride(0) < 0 || H.stride(1) < 0 || H.stride(2) < 0)) { + // Assume that image being written to disk is going to have its transform adjusted, + // so modify the phase encoding scheme appropriately before writing to JSON + for (ssize_t row = 0; row != pe_scheme.rows(); ++row) { + auto new_line = pe_scheme.row (row); + for (ssize_t axis = 0; axis != 3; ++axis) + new_line[axis] = H.stride (order[axis]) > 0 ? pe_scheme(row, order[axis]) : -pe_scheme(row, order[axis]); + pe_scheme.row (row) = new_line; + } + Header H_adj (H); + PhaseEncoding::set_scheme (H_adj, pe_scheme); + for (const auto& kv : H_adj.keyval()) + json[kv.first] = kv.second; + INFO ("Phase encoding information written to JSON file modified according to expected header transform realignment"); + } else { + // Straight copy + for (const auto& kv : H.keyval()) + json[kv.first] = kv.second; + } File::OFStream out (path); out << json.dump(4); } diff --git a/lib/header.cpp b/lib/header.cpp index 957bab6754..73e3fa13d1 100644 --- a/lib/header.cpp +++ b/lib/header.cpp @@ -492,17 +492,19 @@ namespace MR // header, it's necessary here to update it according to the // flips / permutations that have taken place auto pe_scheme = PhaseEncoding::get_scheme (*this); - if (!pe_scheme.rows()) return; - for (ssize_t row = 0; row != pe_scheme.rows(); ++row) { - Eigen::VectorXd new_line (pe_scheme.row (row)); - for (ssize_t axis = 0; axis != 3; ++axis) { - new_line[axis] = pe_scheme(row, perm[axis]); - if (new_line[axis] && flip[axis]) - new_line[axis] = -new_line[axis]; + if (pe_scheme.rows()) { + for (ssize_t row = 0; row != pe_scheme.rows(); ++row) { + Eigen::VectorXd new_line (pe_scheme.row (row)); + for (ssize_t axis = 0; axis != 3; ++axis) { + new_line[axis] = pe_scheme(row, perm[axis]); + if (new_line[axis] && flip[axis]) + new_line[axis] = -new_line[axis]; + } + pe_scheme.row (row) = new_line; } - pe_scheme.row (row) = new_line; + PhaseEncoding::set_scheme (*this, pe_scheme); + INFO ("Phase encoding scheme has been modified according to internal header transform realignment"); } - PhaseEncoding::set_scheme (*this, pe_scheme); } From b6c515326e759025c65e214013aa4ccf5fa54b57 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 17 Oct 2016 18:50:23 +1100 Subject: [PATCH 191/723] JSON import: Rotate phase encoding scheme As discussed in #747. --- lib/file/json_utils.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/file/json_utils.cpp b/lib/file/json_utils.cpp index 210385a39f..9194fd6578 100644 --- a/lib/file/json_utils.cpp +++ b/lib/file/json_utils.cpp @@ -64,6 +64,23 @@ namespace MR } } + + auto pe_scheme = PhaseEncoding::get_scheme (H); + std::vector order; + File::NIfTI::adjust_transform (H, order); + if (pe_scheme.rows() && (order[0] != 0 || order[1] != 1 || order[2] != 2 || H.stride(0) < 0 || H.stride(1) < 0 || H.stride(2) < 0)) { + // The corresponding header may have been rotated on image load prior to the JSON + // being loaded. If this is the case, the phase encoding scheme will need to be + // correspondingly rotated on import. + for (ssize_t row = 0; row != pe_scheme.rows(); ++row) { + auto new_line = pe_scheme.row (row); + for (ssize_t axis = 0; axis != 3; ++axis) + new_line[order[axis]] = H.stride (order[axis]) > 0 ? pe_scheme(row,axis) : -pe_scheme(row,axis); + pe_scheme.row (row) = new_line; + } + PhaseEncoding::set_scheme (H, pe_scheme); + INFO ("Phase encoding information read from JSON file modified according to expected header transform realignment"); + } } From 5e1d77739f0bf4ff5ae006944cf4eb101d06519c Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 18 Oct 2016 13:44:29 +1100 Subject: [PATCH 192/723] Docs: FAQ: Add tck2connectome invlength_invnodevolume --- docs/tutorials/FAQ.rst | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/tutorials/FAQ.rst b/docs/tutorials/FAQ.rst index 73239423d4..acc8d2833b 100644 --- a/docs/tutorials/FAQ.rst +++ b/docs/tutorials/FAQ.rst @@ -235,7 +235,7 @@ streamlines in that edge*. .. code:: - tck2connectome tracks.tck nodes.mif connectome.csf -scale_length -stat_edge mean + tck2connectome tracks.tck nodes.mif connectome.csv -scale_length -stat_edge mean For each streamline, the contribution of that streamline to the relevant edge is *scaled by the length* of that streamline; so, in the absence of any @@ -243,6 +243,19 @@ other scaling, the contribution of that streamline will be equal to the length of the streamline in mm. Finally, for each edge, take the *mean* of the values contributed from all streamlines belonging to that edge. +``tck2connectome -contrast invlength_invnodevolume`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: + + tck2connectome tracks.tck nodes.mif connectome.csv -scale_invlength -scale_invnodevol + +Rather than acting as a single 'contrast', scaling the contribution of each +streamline to the connectome by *both* the inverse of the streamline length +*and* the inverse of the sum of node volumes is now handled using two +separate command-line options. The behaviour is however identical to the +old usage. + Maximum spherical harmonic degree ``lmax`` ------------------------------------------ From ffbb70a790c74fb1c05f99b6d5031ef970bc2253 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 19 Oct 2016 11:51:43 +1100 Subject: [PATCH 193/723] mrregister: add check for mask dimensions --- cmd/mrregister.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/cmd/mrregister.cpp b/cmd/mrregister.cpp index 72b6f95fda..814856f70c 100644 --- a/cmd/mrregister.cpp +++ b/cmd/mrregister.cpp @@ -207,17 +207,21 @@ void run () im2_midway_transformed_path = str(opt[0][1]); } - opt = get_options ("mask2"); - Image im2_mask; - if (opt.size ()) - im2_mask = Image::open(opt[0][0]); - opt = get_options ("mask1"); Image im1_mask; - if (opt.size ()) + if (opt.size ()) { im1_mask = Image::open(opt[0][0]); + check_dimensions (im1_image, im1_mask, 0, 3); + } + opt = get_options ("mask2"); + Image im2_mask; + if (opt.size ()) { + im2_mask = Image::open(opt[0][0]); + check_dimensions (im2_image, im2_mask, 0, 3); + } + // ****** RIGID REGISTRATION OPTIONS ******* Registration::Linear rigid_registration; From bd95f127144693bf050394dcf6b3b8af332c0d7b Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 19 Oct 2016 15:06:29 +1100 Subject: [PATCH 194/723] DICOM import: Fix PE direction Now that the phase encoding direction is rotated whenever the header transform is realigned to RAS, there is no need to explicitly modify the phase encoding direction from LPS to RAS during DICOM import. --- lib/file/dicom/image.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/file/dicom/image.cpp b/lib/file/dicom/image.cpp index 6fadbb4729..6b21ad533c 100644 --- a/lib/file/dicom/image.cpp +++ b/lib/file/dicom/image.cpp @@ -492,10 +492,12 @@ namespace MR { return { }; } // Sign of phase-encoding direction needs to reflect fact that DICOM is in LPS but NIfTI/MRtrix are RAS - int pe_sign = frame.pe_sign; - if (frame.pe_axis == 0 || frame.pe_axis == 1) - pe_sign = -pe_sign; - pe_scheme (n, frame.pe_axis) = pe_sign; + // Reverted; now handled by Header::realign_transform() + //int pe_sign = frame.pe_sign; + //if (frame.pe_axis == 0 || frame.pe_axis == 1) + // pe_sign = -pe_sign; + //pe_scheme (n, frame.pe_axis) = pe_sign; + pe_scheme (n, frame.pe_axis) = frame.pe_sign; if (std::isfinite (frame.bandwidth_per_pixel_phase_encode)) { const default_type effective_echo_spacing = 1.0 / (frame.bandwidth_per_pixel_phase_encode * frame.dim[frame.pe_axis]); pe_scheme(n, 3) = effective_echo_spacing * (frame.dim[frame.pe_axis] - 1); From 41025f5449284ab868ed133a421fdb27e3862a8c Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 20 Oct 2016 08:26:33 +1100 Subject: [PATCH 195/723] population template: made script theoretically cross platform. Not tested on windows --- docs/reference/commands/connectomestats.rst | 6 +- docs/reference/commands/fixelcfestats.rst | 6 +- docs/reference/commands/mrclusterstats.rst | 6 +- docs/reference/commands/vectorstats.rst | 4 +- docs/reference/commands_list.rst | 2 + scripts/population_template | 115 ++++++++++---------- src/registration/linear.h | 2 +- src/stats/permstack.h | 2 + src/stats/permtest.cpp | 14 ++- 9 files changed, 91 insertions(+), 66 deletions(-) diff --git a/docs/reference/commands/connectomestats.rst b/docs/reference/commands/connectomestats.rst index 8cecb82ea4..10b020fc41 100644 --- a/docs/reference/commands/connectomestats.rst +++ b/docs/reference/commands/connectomestats.rst @@ -29,12 +29,16 @@ Options for permutation testing - **-notest** don't perform permutation testing and only output population statistics (effect size, stdev etc) -- **-nperms num** the number of permutations (Default: 5000) +- **-nperms** the number of permutations (Default: 5000) + +- **-permutations num** manually define the permutations (relabelling). The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) - **-nonstationary** perform non-stationarity correction - **-nperms_nonstationary num** the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: 5000) +- **-permutations_nonstationary** manually define the permutations (relabelling) for computing the emprical statistic image for nonstationary correction. The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) + Options for controlling TFCE behaviour ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/fixelcfestats.rst b/docs/reference/commands/fixelcfestats.rst index 7bef303a97..0e453183de 100644 --- a/docs/reference/commands/fixelcfestats.rst +++ b/docs/reference/commands/fixelcfestats.rst @@ -30,12 +30,16 @@ Options for permutation testing - **-notest** don't perform permutation testing and only output population statistics (effect size, stdev etc) -- **-nperms num** the number of permutations (Default: 5000) +- **-nperms** the number of permutations (Default: 5000) + +- **-permutations num** manually define the permutations (relabelling). The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) - **-nonstationary** perform non-stationarity correction - **-nperms_nonstationary num** the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: 5000) +- **-permutations_nonstationary** manually define the permutations (relabelling) for computing the emprical statistic image for nonstationary correction. The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) + Parameters for the Connectivity-based Fixel Enhancement algorithm ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/mrclusterstats.rst b/docs/reference/commands/mrclusterstats.rst index 7d78dc5bdd..e21d49e2a3 100644 --- a/docs/reference/commands/mrclusterstats.rst +++ b/docs/reference/commands/mrclusterstats.rst @@ -29,12 +29,16 @@ Options for permutation testing - **-notest** don't perform permutation testing and only output population statistics (effect size, stdev etc) -- **-nperms num** the number of permutations (Default: 5000) +- **-nperms** the number of permutations (Default: 5000) + +- **-permutations num** manually define the permutations (relabelling). The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) - **-nonstationary** perform non-stationarity correction - **-nperms_nonstationary num** the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: 5000) +- **-permutations_nonstationary** manually define the permutations (relabelling) for computing the emprical statistic image for nonstationary correction. The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) + Options for controlling TFCE behaviour ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/vectorstats.rst b/docs/reference/commands/vectorstats.rst index a45300cb30..891cf30b7f 100644 --- a/docs/reference/commands/vectorstats.rst +++ b/docs/reference/commands/vectorstats.rst @@ -28,7 +28,9 @@ Options for permutation testing - **-notest** don't perform permutation testing and only output population statistics (effect size, stdev etc) -- **-nperms num** the number of permutations (Default: 5000) +- **-nperms** the number of permutations (Default: 5000) + +- **-permutations num** manually define the permutations (relabelling). The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) Standard options ^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index f461dac56f..5fc4e5c4d1 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -153,6 +153,8 @@ List of MRtrix3 commands commands/tck2connectome + commands/tck2fixel + commands/tckconvert commands/tckedit diff --git a/scripts/population_template b/scripts/population_template index 4e0897796e..02c59b31d1 100755 --- a/scripts/population_template +++ b/scripts/population_template @@ -21,6 +21,7 @@ from lib.getUserPath import getUserPath from lib.printMessage import printMessage from lib.runCommand import runCommand from lib.warnMessage import warnMessage +from lib.delFile import delFile try: from numpy import loadtxt, savetxt, dot @@ -180,9 +181,9 @@ for i in inFiles: if prefix not in maskPrefixes: errorMessage ('no matching mask image was found for input image ' + i) index = maskPrefixes.index(prefix) - input.append(Input(i, prefix, lib.app.workingDir + '/' + inputDir + '/', maskFiles[index], lib.app.workingDir + '/' + maskDir + '/')) + input.append(Input(i, prefix, os.path.join(lib.app.workingDir, inputDir), maskFiles[index], os.path.join(lib.app.workingDir, maskDir))) else: - input.append(Input(i, prefix, lib.app.workingDir + '/' + inputDir + '/')) + input.append(Input(i, prefix, os.path.join(lib.app.workingDir, inputDir))) noreorientation = lib.app.args.noreorientation @@ -282,26 +283,26 @@ if useMasks: mask_filenames = [] for i in input: runCommand('mrtransform ' + abspath(i.mask_directory, i.mask_filename) + - ' -interp nearest -template average_header.mif masks_transformed/' + i.mask_filename) - mask_filenames.append('masks_transformed/' + i.mask_filename) + ' -interp nearest -template average_header.mif ' + os.path.join('masks_transformed', i.mask_filename)) + mask_filenames.append(os.path.join('masks_transformed', i.mask_filename)) runCommand('mrmath ' + ' '.join(mask_filenames) + ' max mask_initial.mif' ) runCommand('mrcrop ' + 'average_header.mif -mask mask_initial.mif average_header_cropped.mif') - runCommand('rm -f mask_initial.mif') - runCommand('rm -f average_header.mif') - runCommand('mv average_header_cropped.mif average_header.mif') + delFile('mask_initial.mif') + delFile('average_header.mif') + shutil.move('average_header_cropped.mif', 'average_header.mif') for mask in mask_filenames: - runCommand('rm ' + mask) + delFile(mask) if initial_alignment == 'none': for i in input: - runCommand('mrtransform ' + abspath(i.directory, i.filename) + ' -interp linear -template average_header.mif input_transformed/' + i.prefix + '.mif' + datatype) + runCommand('mrtransform ' + abspath(i.directory, i.filename) + ' -interp linear -template average_header.mif ' + os.path.join('input_transformed', i.prefix + '.mif') + datatype) else: mask = '' for i in input: if useMasks: mask = ' -mask1 ' + abspath(i.mask_directory, i.mask_filename) - output = ' -rigid linear_transforms_initial/' + i.prefix + '.txt' + output = ' -rigid ' + os.path.join('linear_transforms_initial', i.prefix + '.txt') runCommand('mrregister ' + abspath(i.directory, i.filename) + ' average_header.mif' + mask + ' -rigid_scale 1 ' + @@ -313,53 +314,52 @@ else: output) # translate input images to centre of mass without interpolation runCommand('mrtransform ' + abspath(i.directory, i.filename) + - ' -linear linear_transforms_initial/' + i.prefix + '.txt' + + ' -linear ' + os.path.join('linear_transforms_initial', i.prefix + '.txt') + datatype + - ' input_transformed/' + i.prefix + '_translated.mif') + ' ' + os.path.join('input_transformed', i.prefix + '_translated.mif')) if useMasks: runCommand('mrtransform ' + abspath(i.mask_directory, i.mask_filename) + - ' -linear linear_transforms_initial/' + i.prefix + '.txt' + + ' -linear ' + os.path.join('linear_transforms_initial', i.prefix + '.txt') + datatype + - ' masks_transformed/' + i.prefix + '_translated.mif') + ' ' + os.path.join('masks_transformed', i.prefix + '_translated.mif')) # update average space to new extent - runCommand('mraverageheader ' + ' '.join(['input_transformed/' + i.prefix + '_translated.mif' for i in input]) + ' average_header_tight.mif') + runCommand('mraverageheader ' + ' '.join([os.path.join('input_transformed', i.prefix + '_translated.mif') for i in input]) + ' average_header_tight.mif') runCommand('mrpad -uniform 10 average_header_tight.mif average_header.mif -force') - runCommand('rm -f average_header_tight.mif') + delFile('average_header_tight.mif') if useMasks: # reslice masks for i in input: runCommand('mrtransform ' + - ' masks_transformed/' + i.prefix + '_translated.mif' + - ' masks_transformed/' + i.prefix + '.mif' + + ' ' + os.path.join('masks_transformed', i.prefix + '_translated.mif') + + ' ' + os.path.join('masks_transformed', i.prefix + '.mif') + ' -interp nearest -template average_header.mif' + datatype) # crop average space to extent defined by translated masks mask_filenames = [] for i in input: - mask_filenames.append('masks_transformed/' + i.prefix + '.mif') + mask_filenames.append(os.path.join('masks_transformed', i.prefix + '.mif')) runCommand('mrmath ' + ' '.join(mask_filenames) + ' max mask_translated.mif' ) - runCommand('rm -f mask_translated_smooth.mif') runCommand('mrcrop ' + 'average_header.mif -mask mask_translated.mif average_header_cropped.mif') # pad average space to allow for deviation from centre of mass alignment runCommand('mrpad -uniform 10 average_header_cropped.mif -force average_header.mif') - runCommand('rm -f average_header_cropped.mif') + delFile('average_header_cropped.mif') for i in input: runCommand('mrtransform ' + - ' masks_transformed/' + i.prefix + '_translated.mif' + - ' masks_transformed/' + i.prefix + '.mif' + + ' ' + os.path.join('masks_transformed', i.prefix + '_translated.mif') + + ' ' + os.path.join('masks_transformed', i.prefix + '.mif') + ' -interp nearest -template average_header.mif' + datatype + ' -force') - runCommand('rm -f masks_transformed/' + i.prefix + '_translated.mif') - runCommand('rm -f mask_translated.mif') + delFile(os.path.join('masks_transformed', i.prefix + '_translated.mif')) + delFile('mask_translated.mif') # reslice input images for i in input: runCommand('mrtransform ' + - ' input_transformed/' + i.prefix + '_translated.mif' + - ' input_transformed/' + i.prefix + '.mif' + + ' ' + os.path.join('input_transformed', i.prefix + '_translated.mif') + + ' ' + os.path.join('input_transformed', i.prefix + '.mif') + ' -interp linear -template average_header.mif' + datatype) - runCommand('rm -f input_transformed/' + i.prefix + '_translated.mif') + delFile(os.path.join('input_transformed', i.prefix + '_translated.mif')) runCommand('mrmath ' + allindir('input_transformed') + ' mean initial_template.mif') @@ -382,9 +382,9 @@ for level in range(0, len(linear_scales)): scale = ' -rigid_scale ' + str(linear_scales[level]) niter = ' -rigid_niter ' + str(linear_niter[level]) regtype = ' -type rigid' - output = ' -rigid linear_transforms_%i/%s.txt' % (level, i.prefix) + output = ' -rigid ' + os.path.join('linear_transforms_' + str(level), i.prefix + '.txt') if level > 0: - initialise = ' -rigid_init_matrix linear_transforms_%i/%s.txt' % (level-1, i.prefix) + initialise = ' -rigid_init_matrix ' + os.path.join('linear_transforms_' + str(level-1), i.prefix + '.txt') if do_fod_registration: lmax = ' -rigid_lmax ' + str(linear_lmax[level]) else: @@ -392,14 +392,14 @@ for level in range(0, len(linear_scales)): if linear_estimator: metric = ' -rigid_metric.diff.estimator ' + linear_estimator if lib.app.args.verbose: - mrregister_log = ' -info -rigid_log ' + 'log/' + i.filename + "_" + str(level) + '.log' + mrregister_log = ' -info -rigid_log ' + os.path.join('log', i.filename + "_" + str(level) + '.log') else: scale = ' -affine_scale ' + str(linear_scales[level]) niter = ' -affine_niter ' + str(linear_niter[level]) regtype = ' -type affine' - output = ' -affine linear_transforms_%i/%s.txt' % (level, i.prefix) + output = ' -affine ' + os.path.join('linear_transforms_' + str(level), i.prefix + '.txt') if level > 0: - initialise = ' -affine_init_matrix linear_transforms_%i/%s.txt' % (level-1, i.prefix) + initialise = ' -affine_init_matrix ' + os.path.join('linear_transforms_' + str(level-1), i.prefix + '.txt') if do_fod_registration: lmax = ' -affine_lmax ' + str(linear_lmax[level]) else: @@ -407,7 +407,7 @@ for level in range(0, len(linear_scales)): if linear_estimator: metric = ' -affine_metric.diff.estimator ' + linear_estimator if lib.app.args.verbose: - mrregister_log = ' -info -affine_log ' + 'log/' + i.filename + "_" + str(level) + '.log' + mrregister_log = ' -info -affine_log ' + os.path.join('log', i.filename + "_" + str(level) + '.log') runCommand('mrregister ' + abspath(i.directory, i.filename) + ' ' + current_template + @@ -422,7 +422,7 @@ for level in range(0, len(linear_scales)): datatype + output + mrregister_log) - check_linear_transformation('linear_transforms_%i/%s.txt' % (level, i.prefix), pause_on_warn=do_pause_on_warn) + check_linear_transformation( os.path.join('linear_transforms_' + str(level), i.prefix + '.txt'), pause_on_warn=do_pause_on_warn) # Here we ensure the template doesn't drift or scale runCommand('transformcalc ' + allindir('linear_transforms_%i' % level) + ' average linear_transform_average.txt -force -quiet') @@ -432,22 +432,22 @@ for level in range(0, len(linear_scales)): average_inv = loadtxt('linear_transform_average_inv.txt') for i in input: - transform = dot(loadtxt('linear_transforms_%i/%s.txt' % (level, i.prefix)), average_inv) - savetxt('linear_transforms_%i/%s.txt'% (level, i.prefix), transform) + transform = dot(loadtxt(os.path.join('linear_transforms_' + str(level), i.prefix + '.txt')), average_inv) + savetxt(os.path.join('linear_transforms_%i' % level, '%s.txt' % i.prefix), transform) for i in input: runCommand('mrtransform ' + abspath(i.directory, i.filename) + ' -template ' + current_template + - ' -linear linear_transforms_%i/%s.txt' % (level, i.prefix) + - ' input_transformed/' + i.prefix + '.mif' + + ' -linear ' + os.path.join('linear_transforms_' + str(level), i.prefix + '.txt') + + ' input_transformed' + os.path.join(i.prefix + '.mif') + datatype + ' -force') runCommand ('mrmath ' + allindir('input_transformed') + ' mean linear_template' + str(level) + '.mif -force') current_template = 'linear_template' + str(level) + '.mif' - -runCommand('cp ' + allindir('linear_transforms_%i' % level) + ' linear_transforms/') +for filename in os.listdir('linear_transforms_%i' % level): + shutil.copy(os.path.join('linear_transforms_%i' % level, filename), 'linear_transforms') # Create a template mask for nl registration by taking the intersection of all transformed input masks and dilating if useMasks: @@ -455,8 +455,8 @@ if useMasks: runCommand('mrtransform ' + abspath(i.mask_directory, i.mask_filename) + ' -template ' + current_template + ' -interp nearest' + - ' -linear linear_transforms/%s.txt' % i.prefix + - ' masks_transformed/%s.mif' % i.prefix + + ' -linear ' + os.path.join('linear_transforms', str(i.prefix) + '.txt') + + ' ' + os.path.join('masks_transformed', '%s.mif' % i.prefix) + ' -force') runCommand ('mrmath ' + allindir('masks_transformed') + ' min - | maskfilter - median - | maskfilter - dilate -npass 5 init_nl_template_mask.mif -force') current_template_mask = 'init_nl_template_mask.mif' @@ -467,14 +467,14 @@ lib.app.make_dir('warps') for level in range(0, len(nl_scales)): for i in input: if level > 0: - initialise = ' -nl_init warps_%i/%s.mif' % (level-1, i.prefix) + initialise = ' -nl_init ' + os.path.join('warps_' + str(level-1), i.prefix + '.mif') scale = '' else: scale = ' -nl_scale ' + str(nl_scales[level]) if dorigid: - initialise = ' -rigid_init_matrix linear_transforms/' + i.prefix + '.txt' + initialise = ' -rigid_init_matrix ' + os.path.join('linear_transforms', i.prefix + '.txt') else: - initialise = ' -affine_init_matrix linear_transforms/' + i.prefix + '.txt' + initialise = ' -affine_init_matrix ' + os.path.join('linear_transforms', i.prefix + '.txt') if useMasks: mask = ' -mask1 ' + abspath(i.mask_directory, i.mask_filename) + ' -mask2 ' + current_template_mask @@ -489,8 +489,8 @@ for level in range(0, len(nl_scales)): runCommand('mrregister ' + abspath(i.directory, i.filename) + ' ' + current_template + ' -type nonlinear' + ' -nl_niter ' + str(nl_niter[level]) + - ' -nl_warp_full warps_%i/%s.mif' % (level, i.prefix) + - ' -transformed input_transformed/' + i.prefix + '.mif' + + ' -nl_warp_full ' + os.path.join('warps_' + str(level), '%s.mif' % i.prefix) + + ' -transformed ' + os.path.join('input_transformed', i.prefix + '.mif') + ' -nl_update_smooth ' + lib.app.args.nl_update_smooth + ' -nl_disp_smooth ' + lib.app.args.nl_disp_smooth + ' -nl_grad_step ' + lib.app.args.nl_grad_step + @@ -501,12 +501,12 @@ for level in range(0, len(nl_scales)): datatype + lmax) if level > 0: - runCommand('rm warps_%i/%s.mif' % (level-1, i.prefix)) + delFile(os.path.join('warps_' + str(level-1), i.prefix + '.mif')) if useMasks: runCommand('mrtransform ' + abspath(i.mask_directory, i.mask_filename) + ' -template ' + current_template + - ' -warp_full warps_%i/%s.mif' % (level, i.prefix) + - ' masks_transformed/' + i.prefix + '.mif' + + ' -warp_full ' + os.path.join('warps_%i' % level, '%s.mif' % i.prefix) + + ' ' + os.path.join('masks_transformed', i.prefix + '.mif') + ' -interp nearest ' + ' -force') @@ -521,11 +521,11 @@ for level in range(0, len(nl_scales)): if (nl_scales[level] < nl_scales[level + 1]): upsample_factor = nl_scales[level + 1] / nl_scales[level] for i in input: - runCommand('mrresize warps_%i/%s.mif -scale %f tmp.mif' % (level, i.prefix, upsample_factor)) - runCommand('mv tmp.mif warps_%i/%s.mif' % (level, i.prefix)) + runCommand('mrresize ' + os.path.join('warps_%i' % level, '%s.mif' % i.prefix) + ' -scale %f tmp.mif' % upsample_factor) + shutil.move('tmp.mif', os.path.join('warps_' + str(level), '%s.mif' % i.prefix)) else: for i in input: - shutil.move('warps_%i/%s.mif' % (level, i.prefix), 'warps/') + shutil.move(os.path.join('warps_' + str(level), '%s.mif' % i.prefix), 'warps') runCommand('mrconvert ' + current_template + ' ' + getUserPath(lib.app.args.template, True) + lib.app.mrtrixForce) @@ -534,22 +534,21 @@ if lib.app.args.warp_dir: warp_path = getUserPath(lib.app.args.warp_dir, False) if os.path.exists(warp_path): shutil.rmtree(warp_path) - shutil.copytree('warps/', warp_path) + shutil.copytree('warps', warp_path) if lib.app.args.linear_transformations_dir: linear_transformations_path = getUserPath(lib.app.args.linear_transformations_dir, False) if os.path.exists(linear_transformations_path): shutil.rmtree(linear_transformations_path) - shutil.copytree('linear_transforms/', linear_transformations_path) + shutil.copytree('linear_transforms', linear_transformations_path) if lib.app.args.transformed_dir: transformed_path = getUserPath(lib.app.args.transformed_dir, False) if os.path.exists(transformed_path): shutil.rmtree(transformed_path) - shutil.copytree('input_transformed/', transformed_path) + shutil.copytree('input_transformed', transformed_path) if lib.app.args.template_mask: runCommand('mrconvert ' + current_template_mask + ' ' + getUserPath(lib.app.args.template_mask, True) + lib.app.mrtrixForce) lib.app.complete() - diff --git a/src/registration/linear.h b/src/registration/linear.h index a699bd4565..da0cdd28cf 100644 --- a/src/registration/linear.h +++ b/src/registration/linear.h @@ -361,7 +361,7 @@ namespace MR if (do_reorientation && fod_lmax[level] > 0) evaluate.set_directions (aPSF_directions); - INFO("linear registration..."); + INFO ("linear registration..."); for (auto gd_iteration = 0; gd_iteration < gd_repetitions[level]; ++gd_iteration){ if (reg_bbgd) { Math::GradientDescentBB, typename TransformType::UpdateType> diff --git a/src/stats/permstack.h b/src/stats/permstack.h index 4a131576bc..36d837e932 100644 --- a/src/stats/permstack.h +++ b/src/stats/permstack.h @@ -43,6 +43,8 @@ namespace MR public: PermutationStack (const size_t num_permutations, const size_t num_samples, const std::string msg, const bool include_default = true); + PermutationStack (std::vector >& permutations, const size_t num_samples, const std::string msg, const bool include_default = true); + bool operator() (Permutation&); const std::vector& operator[] (size_t index) const { diff --git a/src/stats/permtest.cpp b/src/stats/permtest.cpp index 632010bb8c..84e9931685 100644 --- a/src/stats/permtest.cpp +++ b/src/stats/permtest.cpp @@ -29,17 +29,25 @@ namespace MR using namespace App; OptionGroup result = OptionGroup ("Options for permutation testing") - + Option ("notest", "don't perform permutation testing and only output population statistics (effect size, stdev etc)") - + Option ("nperms", "the number of permutations (Default: " + str(DEFAULT_NUMBER_PERMUTATIONS) + ")") + + Option ("notest", "don't perform permutation testing and only output population statistics (effect size, stdev etc)") + + Option ("nperms", "the number of permutations (Default: " + str(DEFAULT_NUMBER_PERMUTATIONS) + ")") + + Option ("permutations", "manually define the permutations (relabelling). The input should be a text file defining a m x n matrix, " + "where each relabelling is defined as a column vector of size m, and the number of columns, n, defines " + "the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM)") + Argument ("num").type_integer (1); if (include_nonstationarity) { result + Option ("nonstationary", "perform non-stationarity correction") + Option ("nperms_nonstationary", "the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: " + str(DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY) + ")") - + Argument ("num").type_integer (1); + + Argument ("num").type_integer (1) + + Option ("permutations_nonstationary", "manually define the permutations (relabelling) for computing the emprical statistic image for nonstationary correction. " + "The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, " + "and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM " + "(http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM)"); } + return result; } From fb4170612620c6921391a6e30d3abb9edc70ea95 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 20 Oct 2016 10:06:00 +1100 Subject: [PATCH 196/723] Added the ability to specify permutations in text file for fixelcfestats and mrclusterstats --- cmd/connectomestats.cpp | 2 +- cmd/fixelcfestats.cpp | 29 +++++- cmd/mrclusterstats.cpp | 27 +++++- cmd/vectorstats.cpp | 2 +- docs/reference/commands/connectomestats.rst | 6 +- docs/reference/commands/fixelcfestats.rst | 6 +- docs/reference/commands/mrclusterstats.rst | 6 +- docs/reference/commands/vectorstats.rst | 4 +- lib/math/stats/permutation.cpp | 14 +++ lib/math/stats/permutation.h | 4 + src/stats/permstack.cpp | 10 +- src/stats/permstack.h | 6 +- src/stats/permtest.cpp | 12 ++- src/stats/permtest.h | 102 +++++++++++++------- 14 files changed, 163 insertions(+), 67 deletions(-) diff --git a/cmd/connectomestats.cpp b/cmd/connectomestats.cpp index 97055b867c..59d257ed92 100644 --- a/cmd/connectomestats.cpp +++ b/cmd/connectomestats.cpp @@ -271,7 +271,7 @@ void run() vector_type null_distribution (num_perms); vector_type uncorrected_pvalues (num_edges); - Stats::PermTest::run_permutations (glm_ttest, enhancer, num_perms, empirical_statistic, + Stats::PermTest::run_permutations (num_perms, glm_ttest, enhancer, empirical_statistic, enhanced_output, std::shared_ptr(), null_distribution, std::shared_ptr(), uncorrected_pvalues, std::shared_ptr()); diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index f76e6d6500..57ce3a206b 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -139,13 +139,14 @@ void run() { 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 int num_perms = get_option_value ("nperms", DEFAULT_NUMBER_PERMUTATIONS); + int num_perms = get_option_value ("nperms", DEFAULT_NUMBER_PERMUTATIONS); const value_type smooth_std_dev = get_option_value ("smooth", DEFAULT_SMOOTHING_STD) / 2.3548; const value_type connectivity_threshold = get_option_value ("connectivity", DEFAULT_CONNECTIVITY_THRESHOLD); const bool do_nonstationary_adjustment = get_options ("nonstationary").size(); const int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY); const value_type angular_threshold = get_option_value ("angle", DEFAULT_ANGLE_THRESHOLD); + const std::string input_fixel_folder = argument[0]; Header index_header = FixelFormat::find_index_header (input_fixel_folder); auto index_image = index_header.get_image(); @@ -200,6 +201,17 @@ void run() { if (design.rows() != (ssize_t)identifiers.size()) throw Exception ("number of input files does not match number of rows in design matrix"); + // Load permutations file if supplied + opt = get_options("permutations"); + std::vector > permutations; + if (opt.size()) { + permutations = Math::Stats::Permutation::load_permutations_file (opt[0][0]); + num_perms = permutations.size(); + if (permutations[0].size() != (size_t)design.rows()) + throw Exception ("number of rows in the permutations file (" + str(opt[0][0]) + ") does not match number of rows in design matrix"); + } + + // Load contrast matrix: const matrix_type contrast = load_matrix (argument[3]); @@ -388,10 +400,17 @@ void run() { uncorrected_pvalues_neg.reset (new vector_type (num_fixels)); } - Stats::PermTest::run_permutations (glm_ttest, cfe_integrator, num_perms, empirical_cfe_statistic, - cfe_output, cfe_output_neg, - perm_distribution, perm_distribution_neg, - uncorrected_pvalues, uncorrected_pvalues_neg); + if (permutations.size()) { + Stats::PermTest::run_permutations (permutations, glm_ttest, cfe_integrator, empirical_cfe_statistic, + cfe_output, cfe_output_neg, + perm_distribution, perm_distribution_neg, + uncorrected_pvalues, uncorrected_pvalues_neg); + } else { + Stats::PermTest::run_permutations (num_perms, glm_ttest, cfe_integrator, empirical_cfe_statistic, + cfe_output, cfe_output_neg, + perm_distribution, perm_distribution_neg, + uncorrected_pvalues, uncorrected_pvalues_neg); + } ProgressBar progress ("outputting final results"); save_matrix (perm_distribution, Path::join (output_fixel_folder, "perm_dist.txt")); ++progress; diff --git a/cmd/mrclusterstats.cpp b/cmd/mrclusterstats.cpp index 4407513bf1..b7013f2167 100644 --- a/cmd/mrclusterstats.cpp +++ b/cmd/mrclusterstats.cpp @@ -114,7 +114,7 @@ void run() { 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 int num_perms = get_option_value ("nperms", DEFAULT_NUMBER_PERMUTATIONS); + int num_perms = get_option_value ("nperms", DEFAULT_NUMBER_PERMUTATIONS); const int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY); const bool do_26_connectivity = get_options("connectivity").size(); @@ -135,6 +135,16 @@ void run() { if (design.rows() != (ssize_t)subjects.size()) throw Exception ("number of input files does not match number of rows in design matrix"); + // Load permutations file if supplied + auto opt = get_options("permutations"); + std::vector > permutations; + if (opt.size()) { + permutations = Math::Stats::Permutation::load_permutations_file (opt[0][0]); + num_perms = permutations.size(); + if (permutations[0].size() != (size_t)design.rows()) + throw Exception ("number of rows in the permutations file (" + str(opt[0][0]) + ") does not match number of rows in design matrix"); + } + // Load contrast matrix const matrix_type contrast = load_matrix (argument[2]); if (contrast.cols() != design.cols()) @@ -258,10 +268,17 @@ void run() { } if (!get_options ("notest").size()) { - Stats::PermTest::run_permutations (glm, enhancer, num_perms, empirical_enhanced_statistic, - default_cluster_output, default_cluster_output_neg, - perm_distribution, perm_distribution_neg, - uncorrected_pvalue, uncorrected_pvalue_neg); + if (permutations.size()) { + Stats::PermTest::run_permutations (permutations, glm, enhancer, empirical_enhanced_statistic, + default_cluster_output, default_cluster_output_neg, + perm_distribution, perm_distribution_neg, + uncorrected_pvalue, uncorrected_pvalue_neg); + } else { + Stats::PermTest::run_permutations (num_perms, glm, enhancer, empirical_enhanced_statistic, + default_cluster_output, default_cluster_output_neg, + perm_distribution, perm_distribution_neg, + uncorrected_pvalue, uncorrected_pvalue_neg); + } save_matrix (perm_distribution, prefix + "perm_dist.txt"); if (compute_negative_contrast) diff --git a/cmd/vectorstats.cpp b/cmd/vectorstats.cpp index 7615916c4b..e5d7f1f055 100644 --- a/cmd/vectorstats.cpp +++ b/cmd/vectorstats.cpp @@ -171,7 +171,7 @@ void run() vector_type null_distribution (num_perms), uncorrected_pvalues (num_perms); vector_type empirical_distribution; - Stats::PermTest::run_permutations (glm_ttest, enhancer, num_perms, empirical_distribution, + Stats::PermTest::run_permutations (num_perms, glm_ttest, enhancer, empirical_distribution, default_tvalues, std::shared_ptr(), null_distribution, std::shared_ptr(), uncorrected_pvalues, std::shared_ptr()); diff --git a/docs/reference/commands/connectomestats.rst b/docs/reference/commands/connectomestats.rst index 10b020fc41..c2dc2469bd 100644 --- a/docs/reference/commands/connectomestats.rst +++ b/docs/reference/commands/connectomestats.rst @@ -29,15 +29,15 @@ Options for permutation testing - **-notest** don't perform permutation testing and only output population statistics (effect size, stdev etc) -- **-nperms** the number of permutations (Default: 5000) +- **-nperms num** the number of permutations (Default: 5000) -- **-permutations num** manually define the permutations (relabelling). The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) +- **-permutations file** manually define the permutations (relabelling). The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM). Overrides the nperms option. - **-nonstationary** perform non-stationarity correction - **-nperms_nonstationary num** the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: 5000) -- **-permutations_nonstationary** manually define the permutations (relabelling) for computing the emprical statistic image for nonstationary correction. The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) +- **-permutations_nonstationary file** manually define the permutations (relabelling) for computing the emprical statistic image for nonstationary correction. The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) Overrides the nperms_nonstationary option. Options for controlling TFCE behaviour ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/fixelcfestats.rst b/docs/reference/commands/fixelcfestats.rst index 0e453183de..9e43b9cc61 100644 --- a/docs/reference/commands/fixelcfestats.rst +++ b/docs/reference/commands/fixelcfestats.rst @@ -30,15 +30,15 @@ Options for permutation testing - **-notest** don't perform permutation testing and only output population statistics (effect size, stdev etc) -- **-nperms** the number of permutations (Default: 5000) +- **-nperms num** the number of permutations (Default: 5000) -- **-permutations num** manually define the permutations (relabelling). The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) +- **-permutations file** manually define the permutations (relabelling). The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM). Overrides the nperms option. - **-nonstationary** perform non-stationarity correction - **-nperms_nonstationary num** the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: 5000) -- **-permutations_nonstationary** manually define the permutations (relabelling) for computing the emprical statistic image for nonstationary correction. The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) +- **-permutations_nonstationary file** manually define the permutations (relabelling) for computing the emprical statistic image for nonstationary correction. The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) Overrides the nperms_nonstationary option. Parameters for the Connectivity-based Fixel Enhancement algorithm ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/mrclusterstats.rst b/docs/reference/commands/mrclusterstats.rst index e21d49e2a3..2e03a2340e 100644 --- a/docs/reference/commands/mrclusterstats.rst +++ b/docs/reference/commands/mrclusterstats.rst @@ -29,15 +29,15 @@ Options for permutation testing - **-notest** don't perform permutation testing and only output population statistics (effect size, stdev etc) -- **-nperms** the number of permutations (Default: 5000) +- **-nperms num** the number of permutations (Default: 5000) -- **-permutations num** manually define the permutations (relabelling). The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) +- **-permutations file** manually define the permutations (relabelling). The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM). Overrides the nperms option. - **-nonstationary** perform non-stationarity correction - **-nperms_nonstationary num** the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: 5000) -- **-permutations_nonstationary** manually define the permutations (relabelling) for computing the emprical statistic image for nonstationary correction. The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) +- **-permutations_nonstationary file** manually define the permutations (relabelling) for computing the emprical statistic image for nonstationary correction. The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) Overrides the nperms_nonstationary option. Options for controlling TFCE behaviour ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/vectorstats.rst b/docs/reference/commands/vectorstats.rst index 891cf30b7f..5323dd3861 100644 --- a/docs/reference/commands/vectorstats.rst +++ b/docs/reference/commands/vectorstats.rst @@ -28,9 +28,9 @@ Options for permutation testing - **-notest** don't perform permutation testing and only output population statistics (effect size, stdev etc) -- **-nperms** the number of permutations (Default: 5000) +- **-nperms num** the number of permutations (Default: 5000) -- **-permutations num** manually define the permutations (relabelling). The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) +- **-permutations file** manually define the permutations (relabelling). The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM). Overrides the nperms option. Standard options ^^^^^^^^^^^^^^^^ diff --git a/lib/math/stats/permutation.cpp b/lib/math/stats/permutation.cpp index 97bfe18b7f..03af5cb300 100644 --- a/lib/math/stats/permutation.cpp +++ b/lib/math/stats/permutation.cpp @@ -14,6 +14,7 @@ */ #include "math/stats/permutation.h" +#include "math/math.h" namespace MR { @@ -99,6 +100,19 @@ namespace MR } + std::vector > load_permutations_file (std::string filename) { + std::vector > temp = load_matrix_2D_vector (filename); + if (!temp.size()) + throw Exception ("no data found in permutations file: " + str(filename)); + + std::vector > permutations (temp[0].size(), std::vector(temp.size())); + for (std::vector::size_type i = 0; i < temp[0].size(); i++) + for (std::vector::size_type j = 0; j < temp.size(); j++) + permutations[i][j] = temp[j][i]; + return permutations; + } + + } } diff --git a/lib/math/stats/permutation.h b/lib/math/stats/permutation.h index e3cfe33004..a1730dbff2 100644 --- a/lib/math/stats/permutation.h +++ b/lib/math/stats/permutation.h @@ -49,6 +49,10 @@ namespace MR void statistic2pvalue (const vector_type& perm_dist, const vector_type& stats, vector_type& pvalues); + std::vector > load_permutations_file (std::string filename); + + + } } diff --git a/src/stats/permstack.cpp b/src/stats/permstack.cpp index 251c975a02..884a547223 100644 --- a/src/stats/permstack.cpp +++ b/src/stats/permstack.cpp @@ -29,16 +29,22 @@ namespace MR counter (0), progress (msg, num_permutations) { - Math::Stats::Permutation::generate (num_permutations, num_samples, data, include_default); + Math::Stats::Permutation::generate (num_permutations, num_samples, permutations, include_default); } + PermutationStack::PermutationStack (std::vector >& permutations, const std::string msg) : + num_permutations (permutations.size()), + permutations (permutations), + counter (0), + progress (msg, permutations.size()) { } + bool PermutationStack::operator() (Permutation& out) { if (counter < num_permutations) { out.index = counter; - out.data = data[counter++]; + out.data = permutations[counter++]; ++progress; return true; } else { diff --git a/src/stats/permstack.h b/src/stats/permstack.h index 36d837e932..7db2744fb7 100644 --- a/src/stats/permstack.h +++ b/src/stats/permstack.h @@ -43,20 +43,20 @@ namespace MR public: PermutationStack (const size_t num_permutations, const size_t num_samples, const std::string msg, const bool include_default = true); - PermutationStack (std::vector >& permutations, const size_t num_samples, const std::string msg, const bool include_default = true); + PermutationStack (std::vector >& permutations, const std::string msg); bool operator() (Permutation&); const std::vector& operator[] (size_t index) const { - return data[index]; + return permutations[index]; } const size_t num_permutations; protected: + std::vector< std::vector > permutations; size_t counter; ProgressBar progress; - std::vector< std::vector > data; }; diff --git a/src/stats/permtest.cpp b/src/stats/permtest.cpp index 84e9931685..a7c2b10313 100644 --- a/src/stats/permtest.cpp +++ b/src/stats/permtest.cpp @@ -31,20 +31,24 @@ namespace MR OptionGroup result = OptionGroup ("Options for permutation testing") + Option ("notest", "don't perform permutation testing and only output population statistics (effect size, stdev etc)") + Option ("nperms", "the number of permutations (Default: " + str(DEFAULT_NUMBER_PERMUTATIONS) + ")") + + Argument ("num").type_integer (1) + Option ("permutations", "manually define the permutations (relabelling). The input should be a text file defining a m x n matrix, " "where each relabelling is defined as a column vector of size m, and the number of columns, n, defines " - "the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM)") - + Argument ("num").type_integer (1); + "the number of permutations. Can be generated with the palm_quickperms function in PALM (http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM). " + "Overrides the nperms option.") + + Argument ("file").type_file_in(); if (include_nonstationarity) { result + Option ("nonstationary", "perform non-stationarity correction") + Option ("nperms_nonstationary", "the number of permutations used when precomputing the empirical statistic image for nonstationary correction (Default: " + str(DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY) + ")") - + Argument ("num").type_integer (1) + + Argument ("num").type_integer (1) + Option ("permutations_nonstationary", "manually define the permutations (relabelling) for computing the emprical statistic image for nonstationary correction. " "The input should be a text file defining a m x n matrix, where each relabelling is defined as a column vector of size m, " "and the number of columns, n, defines the number of permutations. Can be generated with the palm_quickperms function in PALM " - "(http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM)"); + "(http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/PALM) " + "Overrides the nperms_nonstationary option.") + + Argument ("file").type_file_in(); } diff --git a/src/stats/permtest.h b/src/stats/permtest.h index 92e1ddd939..dcdadb7cf3 100644 --- a/src/stats/permtest.h +++ b/src/stats/permtest.h @@ -52,7 +52,6 @@ namespace MR const App::OptionGroup Options (const bool include_nonstationarity); - /*! A class to pre-compute the empirical enhanced statistic image for non-stationarity correction */ template class PreProcessor { @@ -259,46 +258,79 @@ namespace MR } } + template + inline void run_permutations (PermutationStack& perm_stack, + const StatsType& stats_calculator, + const std::shared_ptr enhancer, + const vector_type& empirical_enhanced_statistic, + const vector_type& default_enhanced_statistics, + const std::shared_ptr default_enhanced_statistics_neg, + vector_type& perm_dist_pos, + std::shared_ptr perm_dist_neg, + vector_type& uncorrected_pvalues, + std::shared_ptr uncorrected_pvalues_neg) + { + std::vector global_uncorrected_pvalue_count (stats_calculator.num_elements(), 0); + std::shared_ptr< std::vector > global_uncorrected_pvalue_count_neg; + if (perm_dist_neg) + global_uncorrected_pvalue_count_neg.reset (new std::vector (stats_calculator.num_elements(), 0)); + { + Processor processor (stats_calculator, enhancer, + empirical_enhanced_statistic, + default_enhanced_statistics, default_enhanced_statistics_neg, + perm_dist_pos, perm_dist_neg, + global_uncorrected_pvalue_count, global_uncorrected_pvalue_count_neg); + Thread::run_queue (perm_stack, Permutation(), Thread::multi (processor)); + } + for (size_t i = 0; i < stats_calculator.num_elements(); ++i) { + uncorrected_pvalues[i] = global_uncorrected_pvalue_count[i] / default_type(perm_stack.num_permutations); + if (perm_dist_neg) + (*uncorrected_pvalues_neg)[i] = (*global_uncorrected_pvalue_count_neg)[i] / default_type(perm_stack.num_permutations); + } - template - inline void run_permutations (const StatsType& stats_calculator, - const std::shared_ptr enhancer, - const size_t num_permutations, - const vector_type& empirical_enhanced_statistic, - const vector_type& default_enhanced_statistics, - const std::shared_ptr default_enhanced_statistics_neg, - vector_type& perm_dist_pos, - std::shared_ptr perm_dist_neg, - vector_type& uncorrected_pvalues, - std::shared_ptr uncorrected_pvalues_neg) - { - std::vector global_uncorrected_pvalue_count (stats_calculator.num_elements(), 0); - std::shared_ptr< std::vector > global_uncorrected_pvalue_count_neg; - if (perm_dist_neg) - global_uncorrected_pvalue_count_neg.reset (new std::vector (stats_calculator.num_elements(), 0)); - - { - PermutationStack permutations (num_permutations, - stats_calculator.num_subjects(), - "running " + str(num_permutations) + " permutations"); - - Processor processor (stats_calculator, enhancer, - empirical_enhanced_statistic, - default_enhanced_statistics, default_enhanced_statistics_neg, - perm_dist_pos, perm_dist_neg, - global_uncorrected_pvalue_count, global_uncorrected_pvalue_count_neg); - Thread::run_queue (permutations, Permutation(), Thread::multi (processor)); } - for (size_t i = 0; i < stats_calculator.num_elements(); ++i) { - uncorrected_pvalues[i] = global_uncorrected_pvalue_count[i] / default_type(num_permutations); - if (perm_dist_neg) - (*uncorrected_pvalues_neg)[i] = (*global_uncorrected_pvalue_count_neg)[i] / default_type(num_permutations); - } - } + template + inline void run_permutations (std::vector>& permutations, + const StatsType& stats_calculator, + const std::shared_ptr enhancer, + const vector_type& empirical_enhanced_statistic, + const vector_type& default_enhanced_statistics, + const std::shared_ptr default_enhanced_statistics_neg, + vector_type& perm_dist_pos, + std::shared_ptr perm_dist_neg, + vector_type& uncorrected_pvalues, + std::shared_ptr uncorrected_pvalues_neg) + { + PermutationStack perm_stack (permutations, "running " + str(permutations.size()) + " permutations"); + + run_permutations (perm_stack, stats_calculator, enhancer, empirical_enhanced_statistic, default_enhanced_statistics, default_enhanced_statistics_neg, + perm_dist_pos, perm_dist_neg, uncorrected_pvalues, uncorrected_pvalues_neg); + } + + + template + inline void run_permutations (const size_t num_permutations, + const StatsType& stats_calculator, + const std::shared_ptr enhancer, + const vector_type& empirical_enhanced_statistic, + const vector_type& default_enhanced_statistics, + const std::shared_ptr default_enhanced_statistics_neg, + vector_type& perm_dist_pos, + std::shared_ptr perm_dist_neg, + vector_type& uncorrected_pvalues, + std::shared_ptr uncorrected_pvalues_neg) + { + PermutationStack perm_stack (num_permutations, stats_calculator.num_subjects(), "running " + str(num_permutations) + " permutations"); + + run_permutations (perm_stack, stats_calculator, enhancer, empirical_enhanced_statistic, default_enhanced_statistics, default_enhanced_statistics_neg, + perm_dist_pos, perm_dist_neg, uncorrected_pvalues, uncorrected_pvalues_neg); + } + + //! @} } From ac36362249297ec6ea4eda90ff62615600810b8e Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 20 Oct 2016 10:10:29 +1100 Subject: [PATCH 197/723] Added the ability to specify permutations in text file for connectomestats and vectorstats --- cmd/connectomestats.cpp | 27 ++++++++++++++++++++++----- cmd/vectorstats.cpp | 27 ++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/cmd/connectomestats.cpp b/cmd/connectomestats.cpp index 59d257ed92..06c05e7c38 100644 --- a/cmd/connectomestats.cpp +++ b/cmd/connectomestats.cpp @@ -166,7 +166,7 @@ void run() throw Exception ("Unknown enhancement algorithm"); } - const size_t num_perms = get_option_value ("nperms", DEFAULT_NUMBER_PERMUTATIONS); + size_t num_perms = get_option_value ("nperms", DEFAULT_NUMBER_PERMUTATIONS); const bool do_nonstationary_adjustment = get_options ("nonstationary").size(); const size_t nperms_nonstationary = get_option_value ("nperms_nonstationarity", DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY); @@ -175,6 +175,16 @@ void run() if (size_t(design.rows()) != filenames.size()) throw Exception ("number of subjects does not match number of rows in design matrix"); + // Load permutations file if supplied + auto opt = get_options("permutations"); + std::vector > permutations; + if (opt.size()) { + permutations = Math::Stats::Permutation::load_permutations_file (opt[0][0]); + num_perms = permutations.size(); + if (permutations[0].size() != (size_t)design.rows()) + throw Exception ("number of rows in the permutations file (" + str(opt[0][0]) + ") does not match number of rows in design matrix"); + } + // Load contrast matrix matrix_type contrast = load_matrix (argument[3]); if (contrast.cols() > design.cols()) @@ -271,10 +281,17 @@ void run() vector_type null_distribution (num_perms); vector_type uncorrected_pvalues (num_edges); - Stats::PermTest::run_permutations (num_perms, glm_ttest, enhancer, empirical_statistic, - enhanced_output, std::shared_ptr(), - null_distribution, std::shared_ptr(), - uncorrected_pvalues, std::shared_ptr()); + if (permutations.size()) { + Stats::PermTest::run_permutations (permutations, glm_ttest, enhancer, empirical_statistic, + enhanced_output, std::shared_ptr(), + null_distribution, std::shared_ptr(), + uncorrected_pvalues, std::shared_ptr()); + } else { + Stats::PermTest::run_permutations (num_perms, glm_ttest, enhancer, empirical_statistic, + enhanced_output, std::shared_ptr(), + null_distribution, std::shared_ptr(), + uncorrected_pvalues, std::shared_ptr()); + } save_vector (null_distribution, output_prefix + "_null_dist.txt"); vector_type pvalue_output (num_edges); diff --git a/cmd/vectorstats.cpp b/cmd/vectorstats.cpp index e5d7f1f055..ecb2e81c65 100644 --- a/cmd/vectorstats.cpp +++ b/cmd/vectorstats.cpp @@ -88,13 +88,23 @@ void run() const vector_type example_data = load_vector (filenames.front()); const size_t num_elements = example_data.size(); - const size_t num_perms = get_option_value ("nperms", DEFAULT_NUMBER_PERMUTATIONS); + size_t num_perms = get_option_value ("nperms", DEFAULT_NUMBER_PERMUTATIONS); // Load design matrix const matrix_type design = load_matrix (argument[2]); if (size_t(design.rows()) != filenames.size()) throw Exception ("number of subjects does not match number of rows in design matrix"); + // Load permutations file if supplied + auto opt = get_options("permutations"); + std::vector > permutations; + if (opt.size()) { + permutations = Math::Stats::Permutation::load_permutations_file (opt[0][0]); + num_perms = permutations.size(); + if (permutations[0].size() != (size_t)design.rows()) + throw Exception ("number of rows in the permutations file (" + str(opt[0][0]) + ") does not match number of rows in design matrix"); + } + // Load contrast matrix matrix_type contrast = load_matrix (argument[3]); if (contrast.cols() > design.cols()) @@ -171,10 +181,17 @@ void run() vector_type null_distribution (num_perms), uncorrected_pvalues (num_perms); vector_type empirical_distribution; - Stats::PermTest::run_permutations (num_perms, glm_ttest, enhancer, empirical_distribution, - default_tvalues, std::shared_ptr(), - null_distribution, std::shared_ptr(), - uncorrected_pvalues, std::shared_ptr()); + if (permutations.size()) { + Stats::PermTest::run_permutations (permutations, glm_ttest, enhancer, empirical_distribution, + default_tvalues, std::shared_ptr(), + null_distribution, std::shared_ptr(), + uncorrected_pvalues, std::shared_ptr()); + } else { + Stats::PermTest::run_permutations (num_perms, glm_ttest, enhancer, empirical_distribution, + default_tvalues, std::shared_ptr(), + null_distribution, std::shared_ptr(), + uncorrected_pvalues, std::shared_ptr()); + } vector_type default_pvalues (num_elements); Math::Stats::Permutation::statistic2pvalue (null_distribution, default_tvalues, default_pvalues); From 6f966172db95b1e994d55435637d0ba919fbc8e0 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 20 Oct 2016 12:55:14 +1100 Subject: [PATCH 198/723] Added option to specify nonstationarity correction permutations in connectomestats, fixelcfestats and mrclusterstats --- cmd/connectomestats.cpp | 21 +++++++++++++++++++-- cmd/fixelcfestats.cpp | 20 ++++++++++++++++++-- cmd/mrclusterstats.cpp | 21 +++++++++++++++++++-- src/stats/permtest.h | 7 ++----- 4 files changed, 58 insertions(+), 11 deletions(-) diff --git a/cmd/connectomestats.cpp b/cmd/connectomestats.cpp index 06c05e7c38..2bc18e95f0 100644 --- a/cmd/connectomestats.cpp +++ b/cmd/connectomestats.cpp @@ -168,7 +168,7 @@ void run() size_t num_perms = get_option_value ("nperms", DEFAULT_NUMBER_PERMUTATIONS); const bool do_nonstationary_adjustment = get_options ("nonstationary").size(); - const size_t nperms_nonstationary = get_option_value ("nperms_nonstationarity", DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY); + size_t nperms_nonstationary = get_option_value ("nperms_nonstationarity", DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY); // Load design matrix const matrix_type design = load_matrix (argument[2]); @@ -185,6 +185,17 @@ void run() throw Exception ("number of rows in the permutations file (" + str(opt[0][0]) + ") does not match number of rows in design matrix"); } + // Load non-stationary correction permutations file if supplied + opt = get_options("permutations_nonstationary"); + std::vector > permutations_nonstationary; + if (opt.size()) { + permutations_nonstationary = Math::Stats::Permutation::load_permutations_file (opt[0][0]); + nperms_nonstationary = permutations.size(); + if (permutations_nonstationary[0].size() != (size_t)design.rows()) + throw Exception ("number of rows in the nonstationary permutations file (" + str(opt[0][0]) + ") does not match number of rows in design matrix"); + } + + // Load contrast matrix matrix_type contrast = load_matrix (argument[3]); if (contrast.cols() > design.cols()) @@ -260,7 +271,13 @@ void run() // If performing non-stationarity adjustment we need to pre-compute the empirical statistic vector_type empirical_statistic; if (do_nonstationary_adjustment) { - Stats::PermTest::precompute_empirical_stat (glm_ttest, enhancer, nperms_nonstationary, empirical_statistic); + if (permutations_nonstationary.size()) { + Stats::PermTest::PermutationStack perm_stack (permutations_nonstationary, "precomputing empirical statistic for non-stationarity adjustment..."); + Stats::PermTest::precompute_empirical_stat (glm_ttest, enhancer, perm_stack, empirical_statistic); + } else { + Stats::PermTest::PermutationStack perm_stack (nperms_nonstationary, design.rows(), "precomputing empirical statistic for non-stationarity adjustment...", true); + Stats::PermTest::precompute_empirical_stat (glm_ttest, enhancer, perm_stack, empirical_statistic); + } save_matrix (mat2vec.V2M (empirical_statistic), output_prefix + "_empirical.csv"); } diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 57ce3a206b..d22109bc6f 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -143,7 +143,7 @@ void run() { const value_type smooth_std_dev = get_option_value ("smooth", DEFAULT_SMOOTHING_STD) / 2.3548; const value_type connectivity_threshold = get_option_value ("connectivity", DEFAULT_CONNECTIVITY_THRESHOLD); const bool do_nonstationary_adjustment = get_options ("nonstationary").size(); - const int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY); + int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY); const value_type angular_threshold = get_option_value ("angle", DEFAULT_ANGLE_THRESHOLD); @@ -211,6 +211,15 @@ void run() { throw Exception ("number of rows in the permutations file (" + str(opt[0][0]) + ") does not match number of rows in design matrix"); } + // Load non-stationary correction permutations file if supplied + opt = get_options("permutations_nonstationary"); + std::vector > permutations_nonstationary; + if (opt.size()) { + permutations_nonstationary = Math::Stats::Permutation::load_permutations_file (opt[0][0]); + nperms_nonstationary = permutations.size(); + if (permutations_nonstationary[0].size() != (size_t)design.rows()) + throw Exception ("number of rows in the nonstationary permutations file (" + str(opt[0][0]) + ") does not match number of rows in design matrix"); + } // Load contrast matrix: const matrix_type contrast = load_matrix (argument[3]); @@ -366,7 +375,14 @@ void run() { // If performing non-stationarity adjustment we need to pre-compute the empirical CFE statistic if (do_nonstationary_adjustment) { - Stats::PermTest::precompute_empirical_stat (glm_ttest, cfe_integrator, nperms_nonstationary, empirical_cfe_statistic); + + if (permutations_nonstationary.size()) { + Stats::PermTest::PermutationStack permutations (permutations_nonstationary, "precomputing empirical statistic for non-stationarity adjustment..."); + Stats::PermTest::precompute_empirical_stat (glm_ttest, cfe_integrator, permutations, empirical_cfe_statistic); + } else { + Stats::PermTest::PermutationStack permutations (nperms_nonstationary, design.rows(), "precomputing empirical statistic for non-stationarity adjustment...", false); + Stats::PermTest::precompute_empirical_stat (glm_ttest, cfe_integrator, permutations, empirical_cfe_statistic); + } output_header.keyval()["nonstationary adjustment"] = str(true); write_fixel_output (Path::join (output_fixel_folder, "cfe_empirical.mif"), empirical_cfe_statistic, output_header); } else { diff --git a/cmd/mrclusterstats.cpp b/cmd/mrclusterstats.cpp index b7013f2167..e6343fd1df 100644 --- a/cmd/mrclusterstats.cpp +++ b/cmd/mrclusterstats.cpp @@ -115,7 +115,7 @@ void run() { const value_type tfce_E = get_option_value ("tfce_e", DEFAULT_TFCE_E); const bool use_tfce = !std::isfinite (cluster_forming_threshold); int num_perms = get_option_value ("nperms", DEFAULT_NUMBER_PERMUTATIONS); - const int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY); + int nperms_nonstationary = get_option_value ("nperms_nonstationary", DEFAULT_NUMBER_PERMUTATIONS_NONSTATIONARITY); const bool do_26_connectivity = get_options("connectivity").size(); const bool do_nonstationary_adjustment = get_options ("nonstationary").size(); @@ -145,6 +145,16 @@ void run() { throw Exception ("number of rows in the permutations file (" + str(opt[0][0]) + ") does not match number of rows in design matrix"); } + // Load non-stationary correction permutations file if supplied + opt = get_options("permutations_nonstationary"); + std::vector > permutations_nonstationary; + if (opt.size()) { + permutations_nonstationary = Math::Stats::Permutation::load_permutations_file (opt[0][0]); + nperms_nonstationary = permutations.size(); + if (permutations_nonstationary[0].size() != (size_t)design.rows()) + throw Exception ("number of rows in the nonstationary permutations file (" + str(opt[0][0]) + ") does not match number of rows in design matrix"); + } + // Load contrast matrix const matrix_type contrast = load_matrix (argument[2]); if (contrast.cols() != design.cols()) @@ -218,7 +228,14 @@ void run() { if (do_nonstationary_adjustment) { if (!use_tfce) throw Exception ("nonstationary adjustment is not currently implemented for threshold-based cluster analysis"); - Stats::PermTest::precompute_empirical_stat (glm, enhancer, nperms_nonstationary, empirical_enhanced_statistic); + if (permutations_nonstationary.size()) { + Stats::PermTest::PermutationStack permutations (permutations_nonstationary, "precomputing empirical statistic for non-stationarity adjustment..."); + Stats::PermTest::precompute_empirical_stat (glm, enhancer, permutations, empirical_enhanced_statistic); + } else { + Stats::PermTest::PermutationStack permutations (nperms_nonstationary, design.rows(), "precomputing empirical statistic for non-stationarity adjustment...", false); + Stats::PermTest::precompute_empirical_stat (glm, enhancer, permutations, empirical_enhanced_statistic); + } + save_matrix (empirical_enhanced_statistic, prefix + "empirical.txt"); } diff --git a/src/stats/permtest.h b/src/stats/permtest.h index dcdadb7cf3..a5e7bb92c7 100644 --- a/src/stats/permtest.h +++ b/src/stats/permtest.h @@ -208,15 +208,12 @@ namespace MR // Precompute the empircal test statistic for non-stationarity adjustment template void precompute_empirical_stat (const StatsType& stats_calculator, const std::shared_ptr enhancer, - const size_t num_permutations, vector_type& empirical_statistic) + PermutationStack& perm_stack, vector_type& empirical_statistic) { std::vector global_enhanced_count (empirical_statistic.size(), 0); - PermutationStack permutations (num_permutations, - stats_calculator.num_subjects(), - "precomputing empirical statistic for non-stationarity adjustment...", false); { PreProcessor preprocessor (stats_calculator, enhancer, empirical_statistic, global_enhanced_count); - Thread::run_queue (permutations, Permutation(), Thread::multi (preprocessor)); + Thread::run_queue (perm_stack, Permutation(), Thread::multi (preprocessor)); } for (ssize_t i = 0; i < empirical_statistic.size(); ++i) { if (global_enhanced_count[i] > 0) From 75bed320f5459c78cc69d572bd18a21eedff9591 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 24 Oct 2016 11:15:38 +1100 Subject: [PATCH 199/723] mrconvert: Fix JSON export for altered output strides --- cmd/mrconvert.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cmd/mrconvert.cpp b/cmd/mrconvert.cpp index 4ed989551f..d764da1e49 100644 --- a/cmd/mrconvert.cpp +++ b/cmd/mrconvert.cpp @@ -352,11 +352,6 @@ void run () } - opt = get_options ("json_export"); - if (opt.size()) - File::JSON::save (header_out, opt[0][0]); - - 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: @@ -385,5 +380,9 @@ void run () copy_permute (header_in, header_out, pos, argument[1]); } + + opt = get_options ("json_export"); + if (opt.size()) + File::JSON::save (header_out, opt[0][0]); } From 60e8887ad6826d164322340788f3e7500a75bd18 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 24 Oct 2016 11:17:52 +1100 Subject: [PATCH 200/723] mrview: New option -norealign Performs the same functionality as the -norealign option in mrinfo: Skips the realignment of image strides & transform to approximately match an axial acquisition. Useful for testing for correct manipulation of orientation-dependent data for such schemes. --- cmd/mrinfo.cpp | 7 ++----- cmd/mrview.cpp | 3 +++ lib/header.cpp | 8 +++++++- lib/header.h | 3 +++ src/gui/mrview/window.cpp | 6 +++++- 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/cmd/mrinfo.cpp b/cmd/mrinfo.cpp index 407e517f98..90cf064c5a 100644 --- a/cmd/mrinfo.cpp +++ b/cmd/mrinfo.cpp @@ -68,11 +68,8 @@ void usage () + Option ("offset", "image intensity offset") + Option ("multiplier", "image intensity multiplier") + Option ("transform", "the voxel to image transformation") - + Option ("norealign", - "do not realign transform to near-default RAS coordinate system (the " - "default behaviour on image load). This is useful to inspect the transform " - "and strides as they are actually stored in the header, rather than as " - "MRtrix interprets them.") + + + NoRealignOption + Option ("property", "any text properties embedded in the image header under the " "specified key (use 'all' to list all keys found)").allow_multiple() diff --git a/cmd/mrview.cpp b/cmd/mrview.cpp index ac554c80e4..e626ed5e36 100644 --- a/cmd/mrview.cpp +++ b/cmd/mrview.cpp @@ -65,6 +65,9 @@ void run () GUI::MRView::Window window; window.show(); + if (MR::App::get_options ("norealign").size()) + Header::do_not_realign_transform = true; + if (argument.size()) { std::vector> list; diff --git a/lib/header.cpp b/lib/header.cpp index 73e3fa13d1..ec9f9a9d7b 100644 --- a/lib/header.cpp +++ b/lib/header.cpp @@ -13,7 +13,6 @@ * */ -#include "app.h" #include "header.h" #include "phase_encoding.h" #include "stride.h" @@ -26,6 +25,13 @@ namespace MR { + const App::Option NoRealignOption + = App::Option ("norealign", + "do not realign transform to near-default RAS coordinate system (the " + "default behaviour on image load). This is useful to inspect the image " + "and/or header contents as they are actually stored in the header, " + "rather than as MRtrix interprets them."); + bool Header::do_not_realign_transform = false; diff --git a/lib/header.h b/lib/header.h index a24509c862..43f7c0188d 100644 --- a/lib/header.h +++ b/lib/header.h @@ -18,6 +18,7 @@ #include +#include "app.h" #include "debug.h" #include "types.h" #include "memory.h" @@ -31,6 +32,8 @@ namespace MR { + extern const App::Option NoRealignOption; + /*! \defgroup ImageAPI Image access * \brief Classes and functions providing access to image data. * diff --git a/src/gui/mrview/window.cpp b/src/gui/mrview/window.cpp index bdf06ea560..d915525ce9 100644 --- a/src/gui/mrview/window.cpp +++ b/src/gui/mrview/window.cpp @@ -1866,7 +1866,11 @@ namespace MR + OptionGroup ("Debugging options") + Option ("fps", "Display frames per second, averaged over the last 10 frames. " - "The maximum over the last 3 seconds is also displayed."); + "The maximum over the last 3 seconds is also displayed.") + + + OptionGroup ("Other options") + + + NoRealignOption; } From 184104cb7669f765597cdcf756948f4db66b4c16 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 24 Oct 2016 14:24:14 +1100 Subject: [PATCH 201/723] progressBar.py: Fix done() function --- scripts/lib/progressBar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/lib/progressBar.py b/scripts/lib/progressBar.py index b915974354..4a323e6ba7 100644 --- a/scripts/lib/progressBar.py +++ b/scripts/lib/progressBar.py @@ -23,7 +23,7 @@ def increment(self, msg=''): self._update() def done(self): - self.counter = target + self.counter = self.target sys.stderr.write('\r' + lib.app.colourPrint + os.path.basename(sys.argv[0]) + ': ' + lib.app.colourClear + '[100%] ' + self.message + lib.app.clearLine + '\n') lib.app.verbosity = self.orig_verbosity - \ No newline at end of file + From 3908a2d01911928a8eb538fd84aeee0b15d3e218 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 25 Oct 2016 16:05:47 +1100 Subject: [PATCH 202/723] dwipreproc: Better syntax for list copying --- scripts/dwipreproc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/dwipreproc b/scripts/dwipreproc index 7231b2ed3f..857c67774a 100755 --- a/scripts/dwipreproc +++ b/scripts/dwipreproc @@ -196,13 +196,13 @@ if manual_pe_dir: # Still construct the manual PE scheme even with 'None' or 'Pair': # there may be information in the header that we need to compare against if PE_design == 'None': - line = manual_pe_dir.copy() + line = list(manual_pe_dir) # Make a copy line.append(trt) dwi_manual_pe_scheme = [ line ] * num_volumes # With 'Pair', also need to construct the manual scheme for SE EPIs elif PE_design == 'Pair': - line = manual_pe_dir.copy() + line = list(manual_pe_dir) line.append(trt) dwi_manual_pe_scheme = [ line ] * num_volumes num_topup_volumes = topup_size[3] @@ -234,7 +234,7 @@ if manual_pe_dir: # if there's one in the header, want to compare to the manually-generated one dwi_manual_pe_scheme = [ ] for index in range(0, num_volumes): - line = manual_pe_dir.copy() + line = list(manual_pe_dir) if grad_matchings[index] < index: line = [ (-i if i else 0.0) for i in line ] line.append(trt) @@ -356,7 +356,7 @@ if lib.app.args.se_epi: else: if not dwi_pe_scheme: errorMessage('Unable to determine phase-encoding design of topup images') - line = dwi_pe_scheme[0].copy() + line = list(dwi_pe_scheme[0]) topup_manual_pe_scheme = [ ' '.join(str(i) for i in line) ] * int(num_topup_volumes/2) line = [ (-i if i else 0.0) for i in line[0:3] ] line.append(trt) From 46dc4a07a2da0e0d8885825cfff90d1345e4c451 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 25 Oct 2016 16:28:37 +1100 Subject: [PATCH 203/723] dwipreproc: Bug fixes --- scripts/dwipreproc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/dwipreproc b/scripts/dwipreproc index 7231b2ed3f..a073aaa0c5 100755 --- a/scripts/dwipreproc +++ b/scripts/dwipreproc @@ -196,13 +196,13 @@ if manual_pe_dir: # Still construct the manual PE scheme even with 'None' or 'Pair': # there may be information in the header that we need to compare against if PE_design == 'None': - line = manual_pe_dir.copy() + line = list(manual_pe_dir) line.append(trt) dwi_manual_pe_scheme = [ line ] * num_volumes # With 'Pair', also need to construct the manual scheme for SE EPIs elif PE_design == 'Pair': - line = manual_pe_dir.copy() + line = list(manual_pe_dir) line.append(trt) dwi_manual_pe_scheme = [ line ] * num_volumes num_topup_volumes = topup_size[3] @@ -234,7 +234,7 @@ if manual_pe_dir: # if there's one in the header, want to compare to the manually-generated one dwi_manual_pe_scheme = [ ] for index in range(0, num_volumes): - line = manual_pe_dir.copy() + line = list(manual_pe_dir) if grad_matchings[index] < index: line = [ (-i if i else 0.0) for i in line ] line.append(trt) @@ -278,7 +278,7 @@ if dwi_pe_scheme: warnMessage('User-defined total readout time does not match what is stored in DWI image header; proceeding with user specification') overwrite_scheme = True if overwrite_scheme: - dwi_pe_scheme = dwi_pe_manual_scheme # May be used later for triggering volume recombination + dwi_pe_scheme = dwi_manual_pe_scheme # May be used later for triggering volume recombination else: dwi_manual_pe_scheme = None # To guarantee that the scheme stored within the header will be used else: @@ -356,7 +356,7 @@ if lib.app.args.se_epi: else: if not dwi_pe_scheme: errorMessage('Unable to determine phase-encoding design of topup images') - line = dwi_pe_scheme[0].copy() + line = list(dwi_pe_scheme[0]) topup_manual_pe_scheme = [ ' '.join(str(i) for i in line) ] * int(num_topup_volumes/2) line = [ (-i if i else 0.0) for i in line[0:3] ] line.append(trt) From 36e05d6f3eb6c17d122c39f4592beea4238907a7 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 25 Oct 2016 17:34:03 +1100 Subject: [PATCH 204/723] getAlgorithmList: Give error if script's source directory is not found --- scripts/lib/getAlgorithmList.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/lib/getAlgorithmList.py b/scripts/lib/getAlgorithmList.py index ecac0916da..f4f7fe2537 100644 --- a/scripts/lib/getAlgorithmList.py +++ b/scripts/lib/getAlgorithmList.py @@ -1,12 +1,16 @@ def getAlgorithmList(): import os, sys from lib.debugMessage import debugMessage + from lib.errorMessage import errorMessage # Build an initial list of possible algorithms, found in the relevant scripts/src/ directory algorithm_list = [] path = os.path.basename(sys.argv[0]) if not path[0].isalpha(): path = '_' + path - src_file_list = os.listdir(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), 'src', path)) + src_file_dir = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), 'src', path) + if not os.path.isdir (src_file_dir): + errorMessage('Unable to query script source directory ' + src_file_dir + ' (have you moved the script?)') + src_file_list = os.listdir(src_file_dir) for filename in src_file_list: filename = filename.split('.') if len(filename) == 2 and filename[1] == 'py' and not filename[0] == '__init__': @@ -14,3 +18,4 @@ def getAlgorithmList(): algorithm_list = sorted(algorithm_list) debugMessage('Found algorithms: ' + str(algorithm_list)) return algorithm_list + From 1fddb68442fd0a2863a52fbab0f1f4ab0e430c71 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 26 Oct 2016 19:42:13 +1100 Subject: [PATCH 205/723] Fix bug in mrmodelfield. Initial commit of mtbin binary --- cmd/mrmodelfield.cpp | 2 +- cmd/mtbin.cpp | 234 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 cmd/mtbin.cpp diff --git a/cmd/mrmodelfield.cpp b/cmd/mrmodelfield.cpp index 2f2bfed377..37877cb7e0 100644 --- a/cmd/mrmodelfield.cpp +++ b/cmd/mrmodelfield.cpp @@ -102,7 +102,7 @@ void run () num_voxels++; } - Eigen::MatrixXf X (num_voxels, 18); + Eigen::MatrixXf X (num_voxels, 19); Eigen::VectorXf y (num_voxels); y.setOnes(); diff --git a/cmd/mtbin.cpp b/cmd/mtbin.cpp new file mode 100644 index 0000000000..72b764f141 --- /dev/null +++ b/cmd/mtbin.cpp @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + + +#include "command.h" +#include "image.h" +#include "algo/loop.h" +#include "adapter/extract.h" +#include "filter/optimal_threshold.h" +#include "transform.h" + +using namespace MR; +using namespace App; + +#define DEFAULT_NORM_VALUE 0.282094 + +void usage () +{ + AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; + + DESCRIPTION + + ""; + + ARGUMENTS + + Argument ("input output", "list of all input and output tissue compartment files. See example usage in the description. " + "Note that any number of tissues can be normalised").type_image_in().allow_multiple(); + + OPTIONS + + Option ("mask", "define the mask to compute the normalisation within. If not supplied this is estimated automatically") + + Argument ("image").type_image_in () + + Option ("value", "specify the value to which the summed tissue compartments will be to (Default: sqrt(1/(4*pi) = " + str(DEFAULT_NORM_VALUE, 3) + ")") + + Argument ("number").type_float () + + Option ("bias", "output the estimated bias field") + + Argument ("image").type_image_out (); +} + + +Eigen::MatrixXf basis_function (Eigen::Vector3 pos) { + float x = (float)pos[0]; + float y = (float)pos[1]; + float z = (float)pos[2]; + Eigen::MatrixXf basis(19, 1); + basis(0) = 1.0; + basis(1) = x; + basis(2) = y; + basis(3) = z; + basis(4) = x * y; + basis(5) = x * z; + basis(6) = y * z; + basis(7) = x * x; + basis(8) = y * y; + basis(9)= z * x; + basis(10)= x * x * y; + basis(11) = x * x * z; + basis(12) = y * y * x; + basis(13) = y * y * z; + basis(14) = z * z * x; + basis(15) = z * z * y; + basis(16) = x * x * x; + basis(17) = y * y * y; + basis(18) = z * z * z; + return basis; +} + + + + +void run () +{ + + if (argument.size() % 2) + throw Exception ("The number of input arguments must be even. There must be an output file provided for every input tissue image"); + + std::vector > input_images; + std::vector > scratch_images; + std::vector
output_headers; + std::vector output_filenames; + + + // Load only first 3D volume of input images + std::vector sh_image_indexes; + for (size_t i = 0; i < argument.size(); i += 2) { + Header header = Header::open (argument[i]); + if (header.ndim() == 4 && header.size(3) > 1) { // assume SH image to extract DC term + auto dc = Adapter::make (header.get_image(), 3, std::vector (1, 0)); + input_images.emplace_back (Image::scratch(dc)); + threaded_copy_with_progress_message ("loading image", dc, input_images[i / 2]); + sh_image_indexes.push_back (i / 2); + } else { + input_images.emplace_back (Image::open(argument[i])); + } + + // check if all inputs have the same dimensions + if (i) + check_dimensions (input_images[0], input_images[i / 2], 0, 3); + + // we can't create the image yet if we want to put the scale factor into the output header + output_filenames.push_back (argument[i + 1]); + output_headers.emplace_back (header); + } + + + // Load or compute a mask to work with + Image mask; + auto opt = get_options("mask"); + if (opt.size()) { + mask = Image::open (opt[0][0]); + } else { + auto summed = Image::scratch (input_images[0]); + for (size_t j = 0; j < input_images.size(); ++j) { + for (auto i = Loop (summed, 0, 3) (summed, input_images[j]); i; ++i) + summed.value() += input_images[j].value(); + } + Filter::OptimalThreshold threshold_filter (summed); + mask = Image::scratch (threshold_filter); + threshold_filter (summed, mask); + } + size_t num_voxels = 0; + for (auto i = Loop (mask) (mask); i; ++i) { + if (mask.value()) + num_voxels++; + } + + if (!num_voxels) + throw Exception ("error in automatic mask generation. Mask contains no voxels"); + + const float normalisation_value = get_option_value ("value", DEFAULT_NORM_VALUE); + + // Initialise bias field to 1 + Eigen::MatrixXf bias_field_weights (19, 1); + bias_field_weights.setZero(); + bias_field_weights(0,0) = 1.0; + + Transform transform (mask); + + + // Iterate until convergence or max iterations performed + ProgressBar progress ("normalising tissue compartments and estimating biasfield..."); + for (size_t iter = 0; iter < 4; ++iter) { + Eigen::MatrixXf scale_factors; + Eigen::MatrixXf X (num_voxels, input_images.size()); + Eigen::MatrixXf y (num_voxels, 1); + y.fill (normalisation_value); + uint32_t counter = 0; + for (auto i = Loop (mask) (mask); i; ++i) { + if (mask.value()) { + Eigen::Vector3 vox (mask.index(0), mask.index(1), mask.index(2)); + Eigen::Vector3 pos = transform.voxel2scanner * vox; + float bias_field_value = basis_function (pos).col(0).dot (bias_field_weights.col(0)); + + for (size_t j = 0; j < input_images.size(); ++j) { + assign_pos_of (mask, 0, 3).to (input_images[j]); + X (counter, j) = bias_field_value * input_images[j].value(); + } + ++counter; + } + } + scale_factors = X.colPivHouseholderQr().solve(y); + progress++; + + Eigen::MatrixXf v (19, 1); + Eigen::MatrixXf A (19, 19); + Eigen::MatrixXf scale_factors_squared = scale_factors.array().pow(2); + + for (auto i = Loop (mask) (mask); i; ++i) { + if (mask.value()) { + float summed = 0.0; + float summed_squared = 0.0; + Eigen::Vector3 vox (mask.index(0), mask.index(1), mask.index(2)); + Eigen::Vector3 pos = transform.voxel2scanner * vox; + auto basis = basis_function (pos); // could precompute + + for (size_t j = 0; j < input_images.size(); ++j) { + assign_pos_of (mask, 0, 3).to (input_images[j]); + summed += scale_factors(j,0) * input_images[j].value(); + summed_squared += scale_factors_squared(j,0) * input_images[j].value(); + } + Eigen::MatrixXf basis_mat = basis * basis.transpose(); + basis_mat.array() *= summed_squared; + A.array() += basis_mat.array(); + + basis.array() *= (summed * normalisation_value); + v.array() += basis.array(); + } + } + bias_field_weights = A.colPivHouseholderQr().solve(v); + progress++; + } + + opt = get_options("bias"); + if (opt.size()) { + auto bias_field = Image::create(opt[0][0], mask); + for (auto i = Loop (bias_field) (bias_field); i; ++i) { + Eigen::Vector3 vox (bias_field.index(0), bias_field.index(1), bias_field.index(2)); + Eigen::Vector3 pos = transform.voxel2scanner * vox; + bias_field.value() = basis_function (pos).col(0).dot (bias_field_weights.col(0)); + } + } + + +// { + + +// for (size_t j = 0; j < input_images.size(); ++j) { +// float scale_factor = bias_field_weights(j,0); + +// output_headers[j].keyval()["normalisation_scale_factor"] = str(scale_factor); +// auto output_image = Image::create (output_filenames[j], output_headers[j]); +// if (std::find (sh_image_indexes.begin(), sh_image_indexes.end(), j) != sh_image_indexes.end()) { +// auto input = Image::open (argument[j * 2]); +// for (auto i = Loop (input) (input, output_image); i; ++i) +// output_image.value() = input.value() * bias_field_weights(j,0); +// } else { +// for (auto i = Loop (input_images[j]) (input_images[j], output_image); i; ++i) +// output_image.value() = input_images[j].value() * bias_field_weights(j,0); +// } +// progress++; +// } +// } +} + From 11798d2e211e78301c747a4c47fbf5fa60532c73 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 27 Oct 2016 09:38:09 +1100 Subject: [PATCH 206/723] Changes to debug mtbin command --- cmd/mtbin.cpp | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/cmd/mtbin.cpp b/cmd/mtbin.cpp index 72b764f141..1ced54dbcd 100644 --- a/cmd/mtbin.cpp +++ b/cmd/mtbin.cpp @@ -48,11 +48,11 @@ void usage () } -Eigen::MatrixXf basis_function (Eigen::Vector3 pos) { +Eigen::MatrixXd basis_function (Eigen::Vector3 pos) { float x = (float)pos[0]; float y = (float)pos[1]; float z = (float)pos[2]; - Eigen::MatrixXf basis(19, 1); + Eigen::MatrixXd basis(19, 1); basis(0) = 1.0; basis(1) = x; basis(2) = y; @@ -134,25 +134,26 @@ void run () num_voxels++; } + save(mask, "computed_mask.mif"); + if (!num_voxels) throw Exception ("error in automatic mask generation. Mask contains no voxels"); const float normalisation_value = get_option_value ("value", DEFAULT_NORM_VALUE); // Initialise bias field to 1 - Eigen::MatrixXf bias_field_weights (19, 1); + Eigen::MatrixXd bias_field_weights (19, 1); bias_field_weights.setZero(); bias_field_weights(0,0) = 1.0; Transform transform (mask); - // Iterate until convergence or max iterations performed ProgressBar progress ("normalising tissue compartments and estimating biasfield..."); - for (size_t iter = 0; iter < 4; ++iter) { - Eigen::MatrixXf scale_factors; - Eigen::MatrixXf X (num_voxels, input_images.size()); - Eigen::MatrixXf y (num_voxels, 1); + for (size_t iter = 0; iter < 1; ++iter) { + Eigen::MatrixXd scale_factors; + Eigen::MatrixXd X (num_voxels, input_images.size()); + Eigen::MatrixXd y (num_voxels, 1); y.fill (normalisation_value); uint32_t counter = 0; for (auto i = Loop (mask) (mask); i; ++i) { @@ -163,7 +164,7 @@ void run () for (size_t j = 0; j < input_images.size(); ++j) { assign_pos_of (mask, 0, 3).to (input_images[j]); - X (counter, j) = bias_field_value * input_images[j].value(); + X (counter, j) = input_images[j].value() / bias_field_value; } ++counter; } @@ -171,24 +172,26 @@ void run () scale_factors = X.colPivHouseholderQr().solve(y); progress++; - Eigen::MatrixXf v (19, 1); - Eigen::MatrixXf A (19, 19); - Eigen::MatrixXf scale_factors_squared = scale_factors.array().pow(2); + + + Eigen::MatrixXd v (19, 1); + Eigen::MatrixXd A (19, 19); + Eigen::MatrixXd scale_factors_squared = scale_factors.array().pow(2); for (auto i = Loop (mask) (mask); i; ++i) { if (mask.value()) { - float summed = 0.0; - float summed_squared = 0.0; Eigen::Vector3 vox (mask.index(0), mask.index(1), mask.index(2)); Eigen::Vector3 pos = transform.voxel2scanner * vox; auto basis = basis_function (pos); // could precompute + double summed = 0.0; + double summed_squared = 0.0; for (size_t j = 0; j < input_images.size(); ++j) { assign_pos_of (mask, 0, 3).to (input_images[j]); summed += scale_factors(j,0) * input_images[j].value(); summed_squared += scale_factors_squared(j,0) * input_images[j].value(); } - Eigen::MatrixXf basis_mat = basis * basis.transpose(); + Eigen::MatrixXd basis_mat = basis * basis.transpose(); basis_mat.array() *= summed_squared; A.array() += basis_mat.array(); @@ -196,7 +199,12 @@ void run () v.array() += basis.array(); } } - bias_field_weights = A.colPivHouseholderQr().solve(v); + + std::cout << "A" << A << std::endl; + std::cout << "v" << v << std::endl; + //bias_field_weights = A.colPivHouseholderQr().solve(v); + bias_field_weights = A.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(v); + std::cout << "weights" << bias_field_weights << std::endl; progress++; } From 2b45ac33bfe87263c12611afb07e84fb3467cabe Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Mon, 31 Oct 2016 21:02:43 +1100 Subject: [PATCH 207/723] Optimisations to mtbin command. Added documentation for mtbin and tck2fixel --- cmd/mtbin.cpp | 131 +++++++++++++------------- docs/reference/commands/maskdump.rst | 5 +- docs/reference/commands/mredit.rst | 10 +- docs/reference/commands/mrinfo.rst | 2 +- docs/reference/commands/mrview.rst | 5 + docs/reference/commands/mtbin.rst | 61 ++++++++++++ docs/reference/commands/tck2fixel.rst | 60 ++++++++++++ docs/reference/commands_list.rst | 2 + docs/reference/scripts/dwipreproc.rst | 4 +- 9 files changed, 209 insertions(+), 71 deletions(-) create mode 100644 docs/reference/commands/mtbin.rst create mode 100644 docs/reference/commands/tck2fixel.rst diff --git a/cmd/mtbin.cpp b/cmd/mtbin.cpp index 1ced54dbcd..315b9deef4 100644 --- a/cmd/mtbin.cpp +++ b/cmd/mtbin.cpp @@ -21,6 +21,7 @@ #include "adapter/extract.h" #include "filter/optimal_threshold.h" #include "transform.h" +#include "math/least_squares.h" using namespace MR; using namespace App; @@ -84,24 +85,18 @@ void run () if (argument.size() % 2) throw Exception ("The number of input arguments must be even. There must be an output file provided for every input tissue image"); + if (argument.size() < 4) + throw Exception ("At least two tissue types must be provided"); + std::vector > input_images; - std::vector > scratch_images; std::vector
output_headers; std::vector output_filenames; // Load only first 3D volume of input images - std::vector sh_image_indexes; for (size_t i = 0; i < argument.size(); i += 2) { Header header = Header::open (argument[i]); - if (header.ndim() == 4 && header.size(3) > 1) { // assume SH image to extract DC term - auto dc = Adapter::make (header.get_image(), 3, std::vector (1, 0)); - input_images.emplace_back (Image::scratch(dc)); - threaded_copy_with_progress_message ("loading image", dc, input_images[i / 2]); - sh_image_indexes.push_back (i / 2); - } else { - input_images.emplace_back (Image::open(argument[i])); - } + input_images.emplace_back (Image::open (argument[i])); // check if all inputs have the same dimensions if (i) @@ -115,11 +110,13 @@ void run () // Load or compute a mask to work with Image mask; + Header header_3D (input_images[0]); + header_3D.ndim() = 3; auto opt = get_options("mask"); if (opt.size()) { mask = Image::open (opt[0][0]); } else { - auto summed = Image::scratch (input_images[0]); + auto summed = Image::scratch (header_3D, "summed tissue compartment image"); for (size_t j = 0; j < input_images.size(); ++j) { for (auto i = Loop (summed, 0, 3) (summed, input_images[j]); i; ++i) summed.value() += input_images[j].value(); @@ -134,7 +131,6 @@ void run () num_voxels++; } - save(mask, "computed_mask.mif"); if (!num_voxels) throw Exception ("error in automatic mask generation. Mask contains no voxels"); @@ -146,71 +142,64 @@ void run () bias_field_weights.setZero(); bias_field_weights(0,0) = 1.0; + // Pre-compute bias field basis functions for all voxels Transform transform (mask); + uint32_t index = 0; + Eigen::MatrixXd bias_field_basis (num_voxels, 19); + for (auto i = Loop(mask)(mask); i; ++i){ + if (mask.value()) { + Eigen::Vector3 vox (mask.index(0), mask.index(1), mask.index(2)); + Eigen::Vector3 pos = transform.voxel2scanner * vox; + bias_field_basis.row (index++) = basis_function (pos).col(0); + } + } // Iterate until convergence or max iterations performed - ProgressBar progress ("normalising tissue compartments and estimating biasfield..."); - for (size_t iter = 0; iter < 1; ++iter) { - Eigen::MatrixXd scale_factors; +// ProgressBar progress ("normalising tissue compartments and estimating biasfield..."); + + for (size_t iter = 0; iter < 5; ++iter) { + Eigen::MatrixXd scale_factors (input_images.size(), 1); + scale_factors.setOnes(); Eigen::MatrixXd X (num_voxels, input_images.size()); Eigen::MatrixXd y (num_voxels, 1); y.fill (normalisation_value); - uint32_t counter = 0; + index = 0; for (auto i = Loop (mask) (mask); i; ++i) { if (mask.value()) { - Eigen::Vector3 vox (mask.index(0), mask.index(1), mask.index(2)); - Eigen::Vector3 pos = transform.voxel2scanner * vox; - float bias_field_value = basis_function (pos).col(0).dot (bias_field_weights.col(0)); - + float bias_field_value = bias_field_basis.row (index).dot (bias_field_weights.col(0)); for (size_t j = 0; j < input_images.size(); ++j) { assign_pos_of (mask, 0, 3).to (input_images[j]); - X (counter, j) = input_images[j].value() / bias_field_value; + X (index, j) = input_images[j].value() / bias_field_value; } - ++counter; + ++index; } } scale_factors = X.colPivHouseholderQr().solve(y); - progress++; - - - Eigen::MatrixXd v (19, 1); - Eigen::MatrixXd A (19, 19); - Eigen::MatrixXd scale_factors_squared = scale_factors.array().pow(2); + std::cout << std::endl << "scale_factors" << std::endl << scale_factors.transpose() << std::endl; +// progress++; + index = 0; for (auto i = Loop (mask) (mask); i; ++i) { if (mask.value()) { - Eigen::Vector3 vox (mask.index(0), mask.index(1), mask.index(2)); - Eigen::Vector3 pos = transform.voxel2scanner * vox; - auto basis = basis_function (pos); // could precompute - - double summed = 0.0; - double summed_squared = 0.0; + double sum = 0.0; for (size_t j = 0; j < input_images.size(); ++j) { assign_pos_of (mask, 0, 3).to (input_images[j]); - summed += scale_factors(j,0) * input_images[j].value(); - summed_squared += scale_factors_squared(j,0) * input_images[j].value(); + sum += scale_factors(j,0) * input_images[j].value() ; } - Eigen::MatrixXd basis_mat = basis * basis.transpose(); - basis_mat.array() *= summed_squared; - A.array() += basis_mat.array(); - - basis.array() *= (summed * normalisation_value); - v.array() += basis.array(); + y(index++, 0) /= sum; } } +// progress++; - std::cout << "A" << A << std::endl; - std::cout << "v" << v << std::endl; - //bias_field_weights = A.colPivHouseholderQr().solve(v); - bias_field_weights = A.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(v); - std::cout << "weights" << bias_field_weights << std::endl; - progress++; + bias_field_weights = bias_field_basis.colPivHouseholderQr().solve(y); + std::cout << std::endl << "weights" << std::endl << bias_field_weights << std::endl; +// progress++; } opt = get_options("bias"); if (opt.size()) { - auto bias_field = Image::create(opt[0][0], mask); + auto bias_field = Image::create(opt[0][0], header_3D); for (auto i = Loop (bias_field) (bias_field); i; ++i) { Eigen::Vector3 vox (bias_field.index(0), bias_field.index(1), bias_field.index(2)); Eigen::Vector3 pos = transform.voxel2scanner * vox; @@ -219,24 +208,38 @@ void run () } -// { + + +} + + +//Eigen::MatrixXd v (19, 1); +//Eigen::MatrixXd A (19, 19); +//Eigen::MatrixXd scale_factors_squared = scale_factors.array().pow(2); +//// uint32_t counter = 0; + +//for (auto i = Loop (mask) (mask); i; ++i) { +// if (mask.value()) { +// Eigen::Vector3 vox (mask.index(0), mask.index(1), mask.index(2)); +// Eigen::Vector3 pos = transform.voxel2scanner * vox; +// auto basis = basis_function (pos); // could precompute + +// double summed = 0.0; +// double summed_squared = 0.0; // for (size_t j = 0; j < input_images.size(); ++j) { -// float scale_factor = bias_field_weights(j,0); - -// output_headers[j].keyval()["normalisation_scale_factor"] = str(scale_factor); -// auto output_image = Image::create (output_filenames[j], output_headers[j]); -// if (std::find (sh_image_indexes.begin(), sh_image_indexes.end(), j) != sh_image_indexes.end()) { -// auto input = Image::open (argument[j * 2]); -// for (auto i = Loop (input) (input, output_image); i; ++i) -// output_image.value() = input.value() * bias_field_weights(j,0); -// } else { -// for (auto i = Loop (input_images[j]) (input_images[j], output_image); i; ++i) -// output_image.value() = input_images[j].value() * bias_field_weights(j,0); -// } -// progress++; +// assign_pos_of (mask, 0, 3).to (input_images[j]); +// summed += scale_factors(j,0) * input_images[j].value(); +// summed_squared += scale_factors_squared(j,0) * input_images[j].value(); // } +// Eigen::MatrixXd basis_mat = basis * basis.transpose(); +// basis_mat.array() *= summed_squared; +// A.array() += basis_mat.array(); + +// basis.array() *= (summed * normalisation_value); +// v.array() += basis.array(); // } -} +//} + diff --git a/docs/reference/commands/maskdump.rst b/docs/reference/commands/maskdump.rst index 8d7550db1c..542cf428b7 100644 --- a/docs/reference/commands/maskdump.rst +++ b/docs/reference/commands/maskdump.rst @@ -8,14 +8,15 @@ Synopsis :: - maskdump [ options ] input + maskdump [ options ] input[ output ] - *input*: the input image. +- *output*: The (optional) output text file. Description ----------- -Print out the locations of all non-zero voxels in a mask image +Print out the locations of all non-zero voxels in a mask image. If no destination file is specified, the voxel locations will be printed to stdout. Options ------- diff --git a/docs/reference/commands/mredit.rst b/docs/reference/commands/mredit.rst index 203d2bb2e7..0438bb1114 100644 --- a/docs/reference/commands/mredit.rst +++ b/docs/reference/commands/mredit.rst @@ -16,12 +16,18 @@ Synopsis Description ----------- -Edit the intensities within an image from the command-line. If only one image path is provided, the image will be edited in-place. If input and output image paths are provided, the original image will not be modified. +Directly edit the intensities within an image from the command-line. 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. Options ------- -- **-voxel position value** Change the intensity of a single voxel +- **-plane axis coord value** fill one or more planes on a particular image axis + +- **-sphere position radius value** draw a sphere with radius in mm + +- **-voxel position value** change the image value within a single voxel + +- **-scanner** indicate that coordinates are specified in scanner space, rather than as voxel coordinates Standard options ^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/mrinfo.rst b/docs/reference/commands/mrinfo.rst index fee3ca94db..2c3fbc2aa1 100644 --- a/docs/reference/commands/mrinfo.rst +++ b/docs/reference/commands/mrinfo.rst @@ -44,7 +44,7 @@ Options - **-transform** the voxel to image transformation -- **-norealign** do not realign transform to near-default RAS coordinate system (the default behaviour on image load). This is useful to inspect the transform and strides as they are actually stored in the header, rather than as MRtrix interprets them. +- **-norealign** do not realign transform to near-default RAS coordinate system (the default behaviour on image load). This is useful to inspect the image and/or header contents as they are actually stored in the header, rather than as MRtrix interprets them. - **-property key** any text properties embedded in the image header under the specified key (use 'all' to list all keys found) diff --git a/docs/reference/commands/mrview.rst b/docs/reference/commands/mrview.rst index 996a83f054..6917334bdb 100644 --- a/docs/reference/commands/mrview.rst +++ b/docs/reference/commands/mrview.rst @@ -69,6 +69,11 @@ Debugging options - **-fps** Display frames per second, averaged over the last 10 frames. The maximum over the last 3 seconds is also displayed. +Other options +^^^^^^^^^^^^^ + +- **-norealign** do not realign transform to near-default RAS coordinate system (the default behaviour on image load). This is useful to inspect the image and/or header contents as they are actually stored in the header, rather than as MRtrix interprets them. + Overlay tool options ^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/reference/commands/mtbin.rst b/docs/reference/commands/mtbin.rst new file mode 100644 index 0000000000..50d7e6ce86 --- /dev/null +++ b/docs/reference/commands/mtbin.rst @@ -0,0 +1,61 @@ +.. _mtbin: + +mtbin +=========== + +Synopsis +-------- + +:: + + mtbin [ options ] input output [ input output ... ] + +- *input output*: list of all input and output tissue compartment files. See example usage in the description. Note that any number of tissues can be normalised + +Description +----------- + + + +Options +------- + +- **-mask image** define the mask to compute the normalisation within. If not supplied this is estimated automatically + +- **-value number** specify the value to which the summed tissue compartments will be to (Default: sqrt(1/(4*pi) = 0.282) + +- **-bias image** output the estimated bias field + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) + +- **-failonwarn** terminate program if a warning is produced + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +-------------- + + + +**Author:** David Raffelt (david.raffelt@florey.edu.au) + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + diff --git a/docs/reference/commands/tck2fixel.rst b/docs/reference/commands/tck2fixel.rst new file mode 100644 index 0000000000..e971ce7c98 --- /dev/null +++ b/docs/reference/commands/tck2fixel.rst @@ -0,0 +1,60 @@ +.. _tck2fixel: + +tck2fixel +=========== + +Synopsis +-------- + +:: + + tck2fixel [ options ] tracks fixel_folder_in fixel_folder_out fixel_data_out + +- *tracks*: the input tracks. +- *fixel_folder_in*: the input fixel folder. Used to define the fixels and their directions +- *fixel_folder_out*: the output fixel folder. This can be the same as the input folder if desired +- *fixel_data_out*: the name of the fixel data image. + +Description +----------- + +compute a fixel TDI map from a tractogram + +Options +------- + +- **-angle value** the max angle threshold for assigning streamline tangents to fixels (Default: 45 degrees) + +Standard options +^^^^^^^^^^^^^^^^ + +- **-info** display information messages. + +- **-quiet** do not display information messages or progress status. + +- **-debug** display debugging messages. + +- **-force** force overwrite of output files. Caution: Using the same file as input and output might cause unexpected behaviour. + +- **-nthreads number** use this number of threads in multi-threaded applications (set to 0 to disable multi-threading) + +- **-failonwarn** terminate program if a warning is produced + +- **-help** display this information page and exit. + +- **-version** display version information and exit. + +-------------- + + + +**Author:** David Raffelt (david.raffelt@florey.edu.au) + +**Copyright:** Copyright (c) 2008-2016 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/ + +MRtrix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +For more details, see www.mrtrix.org + diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index 51345827d5..a11915bfca 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -139,6 +139,8 @@ List of MRtrix3 commands commands/mrview + commands/mtbin + commands/mtnormalise commands/peaks2amp diff --git a/docs/reference/scripts/dwipreproc.rst b/docs/reference/scripts/dwipreproc.rst index 364a2b55f5..0ea962c37e 100644 --- a/docs/reference/scripts/dwipreproc.rst +++ b/docs/reference/scripts/dwipreproc.rst @@ -26,7 +26,7 @@ Options for specifying the acquisition phase-encoding design; note that one of t - **-rpe_none** Specify that no reversed phase-encoding image data is being provided; eddy will perform eddy current and motion correction only -- **-rpe_pair** Specify that a set of images will be provided for use in inhomogeneity field estimation (using the -topup_images option), where it is assumed that the FIRST volume(s) of this image has the same phase-encoding direction as the input DWIs, and the LAST volume(s) has precisely the OPPOSITE phase encoding +- **-rpe_pair** Specify that a set of images will be provided for use in inhomogeneity field estimation (using the -se_epi option), where it is assumed that the FIRST volume(s) of this image has the same phase-encoding direction as the input DWIs, and the LAST volume(s) has precisely the OPPOSITE phase encoding - **-rpe_all** Specify that all DWIs have been acquired with opposing phase-encoding, where it is assumed that the second half of the volumes in the input DWIs have corresponding diffusion sensitisation directions to the first half, but were acquired using the opposite phase-encoding direction @@ -39,7 +39,7 @@ Other options for the dwipreproc script - **-readout_time time** Manually specify the total readout time of the input series (in seconds) -- **-topup_images file** Provide an additional image series that is to be used exclusively by topup for estimating the inhomogeneity field (i.e. it will not form part of the output image series) +- **-se_epi file** Provide an additional image series consisting of spin-echo EPI images, which is to be used exclusively by topup for estimating the inhomogeneity field (i.e. it will not form part of the output image series) - **-json_import JSON_file** Import image header information from an associated JSON file (may be necessary to determine phase encoding information) From b0c9dbd8df48296be68429b39587bcdfe1128111 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 2 Nov 2016 11:35:49 +1100 Subject: [PATCH 208/723] mtbin: normalise bias field to 1 inside mask --- cmd/mtbin.cpp | 214 +++++++++++++++++++++++++++++++------------------- 1 file changed, 134 insertions(+), 80 deletions(-) diff --git a/cmd/mtbin.cpp b/cmd/mtbin.cpp index 315b9deef4..ec6657669a 100644 --- a/cmd/mtbin.cpp +++ b/cmd/mtbin.cpp @@ -27,25 +27,39 @@ using namespace MR; using namespace App; #define DEFAULT_NORM_VALUE 0.282094 +#define DEFAULT_MAXITER_VALUE 100 void usage () { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; DESCRIPTION - + ""; + + "Multi-Tissue Bias field correction and Intensity Normalisation (MTBIN). This script inputs N number of tissue components " + "(e.g. from multi-tissue CSD), and outputs N corrected tissue components. Intensity normalisation is performed by either normalising " + "each tissue type independently with a single global scale factor per tissue or determining a normalise all tissues with the same scale " + "factor (default). Example usage: mtbin wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif. " + "The estimated multiplicative bias field is guaranteed to have a mean of 1 over all voxels within the mask"; ARGUMENTS - + Argument ("input output", "list of all input and output tissue compartment files. See example usage in the description. " + + Argument ("input output", "list of all input and output tissue compartment files. See example usage in the description. " "Note that any number of tissues can be normalised").type_image_in().allow_multiple(); OPTIONS - + Option ("mask", "define the mask to compute the normalisation within. If not supplied this is estimated automatically") + + Option ("mask", "define the mask to compute the normalisation within. If not supplied this is estimated automatically") + Argument ("image").type_image_in () - + Option ("value", "specify the value to which the summed tissue compartments will be to (Default: sqrt(1/(4*pi) = " + str(DEFAULT_NORM_VALUE, 3) + ")") + + + Option ("value", "specify the value to which the summed tissue compartments will be to " + "(Default: sqrt(1/(4*pi) = " + str(DEFAULT_NORM_VALUE, 3) + ")") + Argument ("number").type_float () - + Option ("bias", "output the estimated bias field") - + Argument ("image").type_image_out (); + + + Option ("bias", "output the estimated bias field") + + Argument ("image").type_image_out () + + + Option ("independent", "intensity normalise each tissue type independently") + + + Option ("maxiter", "set the maximum number of iterations. Default(" + str(DEFAULT_MAXITER_VALUE) + "). " + "It will stop before the max iterations if convergence is detected") + + Argument ("number").type_integer(); } @@ -81,7 +95,6 @@ Eigen::MatrixXd basis_function (Eigen::Vector3 pos) { void run () { - if (argument.size() % 2) throw Exception ("The number of input arguments must be even. There must be an output file provided for every input tissue image"); @@ -102,6 +115,8 @@ void run () if (i) check_dimensions (input_images[0], input_images[i / 2], 0, 3); + // TODO check for output and not -force + // we can't create the image yet if we want to put the scale factor into the output header output_filenames.push_back (argument[i + 1]); output_headers.emplace_back (header); @@ -131,6 +146,8 @@ void run () num_voxels++; } + // TODO reject outliers + if (!num_voxels) throw Exception ("error in automatic mask generation. Mask contains no voxels"); @@ -154,92 +171,129 @@ void run () } } + auto bias_field = Image::scratch (header_3D); + for (auto i = Loop(bias_field)(bias_field); i; ++i) + bias_field.value() = 1.0; + + const size_t max_iter = get_option_value ("maxiter", DEFAULT_MAXITER_VALUE); + // Iterate until convergence or max iterations performed -// ProgressBar progress ("normalising tissue compartments and estimating biasfield..."); - - for (size_t iter = 0; iter < 5; ++iter) { - Eigen::MatrixXd scale_factors (input_images.size(), 1); - scale_factors.setOnes(); - Eigen::MatrixXd X (num_voxels, input_images.size()); - Eigen::MatrixXd y (num_voxels, 1); - y.fill (normalisation_value); - index = 0; - for (auto i = Loop (mask) (mask); i; ++i) { - if (mask.value()) { - float bias_field_value = bias_field_basis.row (index).dot (bias_field_weights.col(0)); - for (size_t j = 0; j < input_images.size(); ++j) { - assign_pos_of (mask, 0, 3).to (input_images[j]); - X (index, j) = input_images[j].value() / bias_field_value; + Eigen::MatrixXd scale_factors (input_images.size(), 1); + Eigen::MatrixXd previous_scale_factors (input_images.size(), 1); + + { + ProgressBar progress ("normalising tissue compartments and estimating biasfield..."); + + // TODO check for convergence + size_t iter = 0; + bool converged = false; + while (!converged && iter < max_iter) { + Eigen::MatrixXd X (num_voxels, input_images.size()); + Eigen::MatrixXd y (num_voxels, 1); + y.fill (normalisation_value); + index = 0; + for (auto i = Loop (mask) (mask, bias_field); i; ++i) { + if (mask.value()) { +// float bias_field_value = bias_field_basis.row (index).dot (bias_field_weights.col(0)); + for (size_t j = 0; j < input_images.size(); ++j) { + assign_pos_of (mask, 0, 3).to (input_images[j]); + X (index, j) = input_images[j].value() / bias_field.value(); + } + ++index; } - ++index; } - } - scale_factors = X.colPivHouseholderQr().solve(y); - - std::cout << std::endl << "scale_factors" << std::endl << scale_factors.transpose() << std::endl; -// progress++; - - index = 0; - for (auto i = Loop (mask) (mask); i; ++i) { - if (mask.value()) { - double sum = 0.0; - for (size_t j = 0; j < input_images.size(); ++j) { - assign_pos_of (mask, 0, 3).to (input_images[j]); - sum += scale_factors(j,0) * input_images[j].value() ; + progress++; + scale_factors = X.colPivHouseholderQr().solve(y); + progress++; + + index = 0; + for (auto i = Loop (mask) (mask); i; ++i) { + if (mask.value()) { + double sum = 0.0; + for (size_t j = 0; j < input_images.size(); ++j) { + assign_pos_of (mask, 0, 3).to (input_images[j]); + sum += scale_factors(j,0) * input_images[j].value() ; + } + y(index++, 0) = sum / normalisation_value; } - y(index++, 0) /= sum; } - } -// progress++; - - bias_field_weights = bias_field_basis.colPivHouseholderQr().solve(y); - std::cout << std::endl << "weights" << std::endl << bias_field_weights << std::endl; -// progress++; - } - - opt = get_options("bias"); - if (opt.size()) { - auto bias_field = Image::create(opt[0][0], header_3D); - for (auto i = Loop (bias_field) (bias_field); i; ++i) { - Eigen::Vector3 vox (bias_field.index(0), bias_field.index(1), bias_field.index(2)); - Eigen::Vector3 pos = transform.voxel2scanner * vox; - bias_field.value() = basis_function (pos).col(0).dot (bias_field_weights.col(0)); - } - } - - + progress++; + bias_field_weights = bias_field_basis.colPivHouseholderQr().solve(y); + //bias_field_weights(0,0) = 1.0; // TODO normalise whole field or only within mask? + progress++; + // Normalise the bias field within the mask + double mean = 0.0; + for (auto i = Loop (bias_field) (bias_field, mask); i; ++i) { + if (mask.value()) { + Eigen::Vector3 vox (bias_field.index(0), bias_field.index(1), bias_field.index(2)); + Eigen::Vector3 pos = transform.voxel2scanner * vox; + bias_field.value() = basis_function (pos).col(0).dot (bias_field_weights.col(0)); + mean += bias_field.value(); + } + } + progress++; + mean /= num_voxels; + for (auto i = Loop (bias_field) (bias_field, mask); i; ++i) { + if (mask.value()) + bias_field.value() /= mean; + } + iter++; -} + if (iter) { + Eigen::MatrixXd diff = (previous_scale_factors.array() - scale_factors.array()); + for (size_t i = 0; i < diff.rows(); ++i) + diff(i,0) = std::abs(diff(i,0)); + std::cout << "diff" << diff << std::endl; + } + previous_scale_factors = scale_factors; +// float percent_change = diff.col(0).sum() / previous_scale_factors.col(0).sum(); +// std::cout << "prevous" << previous_scale_factors << std::endl; +// std::cout << "percent change " << percent_change << std::endl; + progress++; + } + } -//Eigen::MatrixXd v (19, 1); -//Eigen::MatrixXd A (19, 19); -//Eigen::MatrixXd scale_factors_squared = scale_factors.array().pow(2); -//// uint32_t counter = 0; -//for (auto i = Loop (mask) (mask); i; ++i) { -// if (mask.value()) { -// Eigen::Vector3 vox (mask.index(0), mask.index(1), mask.index(2)); -// Eigen::Vector3 pos = transform.voxel2scanner * vox; -// auto basis = basis_function (pos); // could precompute + // Construct entire bias field (including outside the mask) + Image bias_field_output; + opt = get_options("bias"); + if (opt.size()) + bias_field_output = Image::create (opt[0][0], header_3D); + else + bias_field_output = Image::scratch (header_3D); + + for (auto i = Loop (bias_field_output) (bias_field_output); i; ++i) { + Eigen::Vector3 vox (bias_field_output.index(0), bias_field_output.index(1), bias_field_output.index(2)); + Eigen::Vector3 pos = transform.voxel2scanner * vox; + bias_field_output.value() = basis_function (pos).col(0).dot (bias_field_weights.col(0)); + } -// double summed = 0.0; -// double summed_squared = 0.0; -// for (size_t j = 0; j < input_images.size(); ++j) { -// assign_pos_of (mask, 0, 3).to (input_images[j]); -// summed += scale_factors(j,0) * input_images[j].value(); -// summed_squared += scale_factors_squared(j,0) * input_images[j].value(); -// } -// Eigen::MatrixXd basis_mat = basis * basis.transpose(); -// basis_mat.array() *= summed_squared; -// A.array() += basis_mat.array(); -// basis.array() *= (summed * normalisation_value); -// v.array() += basis.array(); -// } -//} + // compute mean of all scale factors in the log domain + opt = get_options("independent"); + if (!opt.size()) { + float mean = 0.0; + for (int i = 0; i < scale_factors.size(); ++i) + mean += std::log(scale_factors(i, 0)); + mean /= scale_factors.size(); + mean = std::exp (mean); + scale_factors.fill (mean); + } + // output bias corrected and normalised tissue maps + ProgressBar progress ("writing output files", input_images.size()); + for (size_t j = 0; j < output_filenames.size(); ++j) { + output_headers[j].keyval()["normalisation_scale_factor"] = str(scale_factors(j, 0)); + auto output_image = Image::create (output_filenames[j], output_headers[j]); + for (auto i = Loop (output_image) (output_image, input_images[j]); i; ++i) { + assign_pos_of (output_image, 0, 3).to (bias_field_output); + output_image.value() = scale_factors(j, 0) * input_images[j].value() / bias_field_output.value(); + } + progress++; + } +} From dc3b21597154fa332ce6486fc8ac7ba68f1b42b0 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 2 Nov 2016 12:51:32 +1100 Subject: [PATCH 209/723] FOD FMLS segmenter: Remove thresholds based on negative lobes In testing, it was found that disabling the mechanism whereby positive FOD lobes were rejected based on their peak amplitude relative to the average peak amplitude of negative FOD lobes resulted in much cleaner fixel results. Given that we are now predominantly using a hard non-negativity constraint, even with single-shell data, it makes sense that FOD segmentation decisions should not be made based around the relative sizes of negative lobes. Therefore, both of such mechanisms have been removed, and replaced by a simple FOD lobe integral threshold (which by default is set to zero). --- docs/reference/commands/fod2fixel.rst | 4 +- src/dwi/fmls.cpp | 72 ++++++++------------------- src/dwi/fmls.h | 37 ++++++-------- 3 files changed, 38 insertions(+), 75 deletions(-) diff --git a/docs/reference/commands/fod2fixel.rst b/docs/reference/commands/fod2fixel.rst index a9b3bf83f3..a217f44241 100644 --- a/docs/reference/commands/fod2fixel.rst +++ b/docs/reference/commands/fod2fixel.rst @@ -34,9 +34,7 @@ Metric values for fixel-based sparse output images FOD FMLS segmenter options ^^^^^^^^^^^^^^^^^^^^^^^^^^ -- **-fmls_ratio_integral_to_neg value** threshold the ratio between the integral of a positive FOD lobe, and the integral of the largest negative lobe. Any lobe that fails to exceed the integral dictated by this ratio will be discarded. Default: 0. - -- **-fmls_ratio_peak_to_mean_neg value** threshold the ratio between the peak amplitude of a positive FOD lobe, and the mean peak amplitude of all negative lobes. Any lobe that fails to exceed the peak amplitude dictated by this ratio will be discarded. Default: 1. +- **-fmls_integral value** threshold absolute numerical integral of positive FOD lobes. Any lobe for which the integral is smaller than this threshold will be discarded. Default: 0. - **-fmls_peak_value value** threshold the raw peak amplitude of positive FOD lobes. Any lobe for which the peak amplitude is smaller than this threshold will be discarded. Default: 0.1. diff --git a/src/dwi/fmls.cpp b/src/dwi/fmls.cpp index 7a36ffc543..6f59f1a4a2 100644 --- a/src/dwi/fmls.cpp +++ b/src/dwi/fmls.cpp @@ -30,22 +30,16 @@ namespace MR { const App::OptionGroup FMLSSegmentOption = App::OptionGroup ("FOD FMLS segmenter options") - + App::Option ("fmls_ratio_integral_to_neg", - "threshold the ratio between the integral of a positive FOD lobe, and the integral of the largest negative lobe. " - "Any lobe that fails to exceed the integral dictated by this ratio will be discarded. " - "Default: " + str(FMLS_RATIO_TO_NEGATIVE_LOBE_INTEGRAL_DEFAULT, 2) + ".") - + App::Argument ("value").type_float (0.0) - - + App::Option ("fmls_ratio_peak_to_mean_neg", - "threshold the ratio between the peak amplitude of a positive FOD lobe, and the mean peak amplitude of all negative lobes. " - "Any lobe that fails to exceed the peak amplitude dictated by this ratio will be discarded. " - "Default: " + str(FMLS_RATIO_TO_NEGATIVE_LOBE_MEAN_PEAK_DEFAULT, 2) + ".") + + App::Option ("fmls_integral", + "threshold absolute numerical integral of positive FOD lobes. " + "Any lobe for which the integral is smaller than this threshold will be discarded. " + "Default: " + str(FMLS_INTEGRAL_THRESHOLD_DEFAULT, 2) + ".") + App::Argument ("value").type_float (0.0) + App::Option ("fmls_peak_value", "threshold the raw peak amplitude of positive FOD lobes. " "Any lobe for which the peak amplitude is smaller than this threshold will be discarded. " - "Default: " + str(FMLS_PEAK_VALUE_THRESHOLD, 2) + ".") + "Default: " + str(FMLS_PEAK_VALUE_THRESHOLD_DEFAULT, 2) + ".") + App::Argument ("value").type_float (0.0) + App::Option ("fmls_no_thresholds", @@ -55,7 +49,7 @@ namespace MR { "specify the amplitude ratio between a sample and the smallest peak amplitude of the adjoining lobes, above which the lobes will be merged. " "This is the relative amplitude between the smallest of two adjoining lobes, and the 'bridge' between the two lobes. " "A value of 1.0 will never merge two peaks into a single lobe; a value of 0.0 will always merge lobes unless they are bisected by a zero crossing. " - "Default: " + str(FMLS_RATIO_TO_PEAK_VALUE_DEFAULT, 2) + ".") + "Default: " + str(FMLS_RATIO_TO_PEAK_VALUE_TO_MERGE_DEFAULT, 2) + ".") + App::Argument ("value").type_float (0.0, 1.0); @@ -68,26 +62,16 @@ namespace MR { auto opt = get_options ("fmls_no_thresholds"); const bool no_thresholds = opt.size(); if (no_thresholds) { - segmenter.set_ratio_to_negative_lobe_integral (0.0f); - segmenter.set_ratio_to_negative_lobe_mean_peak (0.0f); - segmenter.set_peak_value_threshold (0.0f); - } - - opt = get_options ("fmls_ratio_integral_to_neg"); - if (opt.size()) { - if (no_thresholds) { - WARN ("Option -fmls_ratio_integral_to_neg ignored: -fmls_no_thresholds overrides this"); - } else { - segmenter.set_ratio_to_negative_lobe_integral (default_type(opt[0][0])); - } + segmenter.set_integral_threshold (0.0); + segmenter.set_peak_value_threshold (0.0); } - opt = get_options ("fmls_ratio_peak_to_mean_neg"); + opt = get_options ("fmls_integral"); if (opt.size()) { if (no_thresholds) { - WARN ("Option -fmls_ratio_peak_to_mean_neg ignored: -fmls_no_thresholds overrides this"); + WARN ("Option -fmls_integral ignored: -fmls_no_thresholds overrides this"); } else { - segmenter.set_ratio_to_negative_lobe_mean_peak (default_type(opt[0][0])); + segmenter.set_integral_threshold (default_type(opt[0][0])); } } @@ -153,16 +137,15 @@ namespace MR { Segmenter::Segmenter (const DWI::Directions::Set& directions, const size_t l) : - dirs (directions), - lmax (l), - precomputer (new Math::SH::PrecomputedAL (lmax, 2 * dirs.size())), - ratio_to_negative_lobe_integral (FMLS_RATIO_TO_NEGATIVE_LOBE_INTEGRAL_DEFAULT), - ratio_to_negative_lobe_mean_peak (FMLS_RATIO_TO_NEGATIVE_LOBE_MEAN_PEAK_DEFAULT), - peak_value_threshold (FMLS_PEAK_VALUE_THRESHOLD), - ratio_of_peak_value_to_merge (FMLS_RATIO_TO_PEAK_VALUE_DEFAULT), - create_null_lobe (false), - create_lookup_table (true), - dilate_lookup_table (false) + dirs (directions), + lmax (l), + precomputer (new Math::SH::PrecomputedAL (lmax, 2 * dirs.size())), + integral_threshold (FMLS_INTEGRAL_THRESHOLD_DEFAULT), + peak_value_threshold (FMLS_PEAK_VALUE_THRESHOLD_DEFAULT), + ratio_of_peak_value_to_merge (FMLS_RATIO_TO_PEAK_VALUE_TO_MERGE_DEFAULT), + create_null_lobe (false), + create_lookup_table (true), + dilate_lookup_table (false) { Eigen::Matrix az_el_pairs (dirs.size(), 2); for (size_t row = 0; row != dirs.size(); ++row) { @@ -274,22 +257,9 @@ namespace MR { for (const auto& i : retrospective_assignments) out[i.second].add (i.first, values[i.first], (*weights)[i.first]); - default_type mean_neg_peak = 0.0, max_neg_integral = 0.0; - uint32_t neg_lobe_count = 0; - for (const auto& i : out) { - if (i.is_negative()) { - mean_neg_peak += i.get_max_peak_value(); - ++neg_lobe_count; - max_neg_integral = std::max (max_neg_integral, default_type(i.get_integral())); - } - } - - const default_type min_peak_amp = ratio_to_negative_lobe_mean_peak * (mean_neg_peak / default_type(neg_lobe_count)); - const default_type min_integral = ratio_to_negative_lobe_integral * max_neg_integral; - for (auto i = out.begin(); i != out.end();) { // Empty increment - if (i->is_negative() || i->get_max_peak_value() < std::max (min_peak_amp, peak_value_threshold) || i->get_integral() < min_integral) { + if (i->is_negative() || i->get_max_peak_value() < peak_value_threshold || i->get_integral() < integral_threshold) { i = out.erase (i); } else { diff --git a/src/dwi/fmls.h b/src/dwi/fmls.h index 22b7f867d0..9f4035a52a 100644 --- a/src/dwi/fmls.h +++ b/src/dwi/fmls.h @@ -29,11 +29,9 @@ - -#define FMLS_RATIO_TO_NEGATIVE_LOBE_INTEGRAL_DEFAULT 0.0 -#define FMLS_RATIO_TO_NEGATIVE_LOBE_MEAN_PEAK_DEFAULT 1.0 // Peak amplitude needs to be greater than the mean negative peak -#define FMLS_PEAK_VALUE_THRESHOLD 0.1 // Throw out anything that's below the CSD regularisation threshold -#define FMLS_RATIO_TO_PEAK_VALUE_DEFAULT 1.0 // By default, turn all peaks into lobes (discrete peaks are never merged) +#define FMLS_INTEGRAL_THRESHOLD_DEFAULT 0.0 // By default, don't threshold by integral (tough to get a good number) +#define FMLS_PEAK_VALUE_THRESHOLD_DEFAULT 0.1 +#define FMLS_RATIO_TO_PEAK_VALUE_TO_MERGE_DEFAULT 1.0 // By default, turn all peaks into lobes (discrete peaks are never merged) // By default, the mean direction of each FOD lobe is calculated by taking a weighted average of the @@ -246,20 +244,18 @@ namespace MR bool operator() (const SH_coefs&, FOD_lobes&) const; - default_type get_ratio_to_negative_lobe_integral () const { return ratio_to_negative_lobe_integral; } - void set_ratio_to_negative_lobe_integral (const default_type i) { ratio_to_negative_lobe_integral = i; } - default_type get_ratio_to_negative_lobe_mean_peak () const { return ratio_to_negative_lobe_mean_peak; } - void set_ratio_to_negative_lobe_mean_peak (const default_type i) { ratio_to_negative_lobe_mean_peak = i; } - default_type get_peak_value_threshold () const { return peak_value_threshold; } - void set_peak_value_threshold (const default_type i) { peak_value_threshold = i; } - default_type get_ratio_of_peak_value_to_merge () const { return ratio_of_peak_value_to_merge; } - void set_ratio_of_peak_value_to_merge (const default_type i) { ratio_of_peak_value_to_merge = i; } - bool get_create_null_lobe () const { return create_null_lobe; } - void set_create_null_lobe (const bool i) { create_null_lobe = i; verify_settings(); } - bool get_create_lookup_table () const { return create_lookup_table; } - void set_create_lookup_table (const bool i) { create_lookup_table = i; verify_settings(); } - bool get_dilate_lookup_table () const { return dilate_lookup_table; } - void set_dilate_lookup_table (const bool i) { dilate_lookup_table = i; verify_settings(); } + default_type get_integral_threshold () const { return integral_threshold; } + void set_integral_threshold (const default_type i) { integral_threshold = i; } + default_type get_peak_value_threshold () const { return peak_value_threshold; } + void set_peak_value_threshold (const default_type i) { peak_value_threshold = i; } + default_type get_ratio_of_peak_value_to_merge () const { return ratio_of_peak_value_to_merge; } + void set_ratio_of_peak_value_to_merge (const default_type i) { ratio_of_peak_value_to_merge = i; } + bool get_create_null_lobe () const { return create_null_lobe; } + void set_create_null_lobe (const bool i) { create_null_lobe = i; verify_settings(); } + bool get_create_lookup_table () const { return create_lookup_table; } + void set_create_lookup_table (const bool i) { create_lookup_table = i; verify_settings(); } + bool get_dilate_lookup_table () const { return dilate_lookup_table; } + void set_dilate_lookup_table (const bool i) { dilate_lookup_table = i; verify_settings(); } private: @@ -272,8 +268,7 @@ namespace MR std::shared_ptr> precomputer; std::shared_ptr weights; - default_type ratio_to_negative_lobe_integral; // Integral of positive lobe must be at least this ratio larger than the largest negative lobe integral - default_type ratio_to_negative_lobe_mean_peak; // Peak value of positive lobe must be at least this ratio larger than the mean negative lobe peak + default_type integral_threshold; // Integral of positive lobe must be at least this value default_type peak_value_threshold; // Absolute threshold for the peak amplitude of the lobe default_type ratio_of_peak_value_to_merge; // Determines whether two lobes get agglomerated into one, depending on the FOD amplitude at the current point and how it compares to the peak amplitudes of the lobes to which it could be assigned bool create_null_lobe; // If this is set, an additional lobe will be created after segmentation with zero size, containing all directions not assigned to any other lobe From 2e08215b5beffed0e0ad24e473d3c71a2b78e569 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 2 Nov 2016 16:31:58 +1100 Subject: [PATCH 210/723] mtbin: added outlier rejection to mask calculation --- cmd/mtbin.cpp | 305 +++++++++++++++++------------- docs/reference/commands/mtbin.rst | 8 +- lib/filter/optimal_threshold.h | 2 +- 3 files changed, 177 insertions(+), 138 deletions(-) diff --git a/cmd/mtbin.cpp b/cmd/mtbin.cpp index ec6657669a..87b516042a 100644 --- a/cmd/mtbin.cpp +++ b/cmd/mtbin.cpp @@ -20,6 +20,8 @@ #include "algo/loop.h" #include "adapter/extract.h" #include "filter/optimal_threshold.h" +#include "filter/mask_clean.h" +#include "filter/connected_components.h" #include "transform.h" #include "math/least_squares.h" @@ -59,38 +61,53 @@ void usage () + Option ("maxiter", "set the maximum number of iterations. Default(" + str(DEFAULT_MAXITER_VALUE) + "). " "It will stop before the max iterations if convergence is detected") - + Argument ("number").type_integer(); + + Argument ("number").type_integer() + + + Option ("check", "check the automatically computed mask") + + Argument ("image").type_image_out (); } -Eigen::MatrixXd basis_function (Eigen::Vector3 pos) { - float x = (float)pos[0]; - float y = (float)pos[1]; - float z = (float)pos[2]; - Eigen::MatrixXd basis(19, 1); - basis(0) = 1.0; - basis(1) = x; - basis(2) = y; - basis(3) = z; - basis(4) = x * y; - basis(5) = x * z; - basis(6) = y * z; - basis(7) = x * x; - basis(8) = y * y; - basis(9)= z * x; - basis(10)= x * x * y; - basis(11) = x * x * z; - basis(12) = y * y * x; - basis(13) = y * y * z; - basis(14) = z * z * x; - basis(15) = z * z * y; - basis(16) = x * x * x; - basis(17) = y * y * y; - basis(18) = z * z * z; - return basis; +FORCE_INLINE Eigen::MatrixXd basis_function (const Eigen::Vector3 pos) { + float x = (float)pos[0]; + float y = (float)pos[1]; + float z = (float)pos[2]; + Eigen::MatrixXd basis(19, 1); + basis(0) = 1.0; + basis(1) = x; + basis(2) = y; + basis(3) = z; + basis(4) = x * y; + basis(5) = x * z; + basis(6) = y * z; + basis(7) = x * x; + basis(8) = y * y; + basis(9)= z * x; + basis(10)= x * x * y; + basis(11) = x * x * z; + basis(12) = y * y * x; + basis(13) = y * y * z; + basis(14) = z * z * x; + basis(15) = z * z * y; + basis(16) = x * x * x; + basis(17) = y * y * y; + basis(18) = z * z * z; + return basis; } +FORCE_INLINE void compute_mask (Image& summed, Image& mask) { + LogLevelLatch level (0); + Filter::OptimalThreshold threshold_filter (summed); + if (!mask.valid()) + mask = Image::scratch (threshold_filter); + threshold_filter (summed, mask); + Filter::ConnectedComponents connected_filter (mask); + connected_filter.set_largest_only (true); + connected_filter (mask, mask); + Filter::MaskClean clean_filter (mask); + clean_filter (mask, mask); +} void run () @@ -101,14 +118,15 @@ void run () if (argument.size() < 4) throw Exception ("At least two tissue types must be provided"); + ProgressBar progress ("performing intensity normalisation and bias field correction..."); std::vector > input_images; std::vector
output_headers; std::vector output_filenames; - // Load only first 3D volume of input images + // Open input images and check for output for (size_t i = 0; i < argument.size(); i += 2) { - Header header = Header::open (argument[i]); + progress++; input_images.emplace_back (Image::open (argument[i])); // check if all inputs have the same dimensions @@ -118,163 +136,174 @@ void run () // TODO check for output and not -force // we can't create the image yet if we want to put the scale factor into the output header + output_headers.emplace_back (Header::open (argument[i])); output_filenames.push_back (argument[i + 1]); - output_headers.emplace_back (header); } - // Load or compute a mask to work with Image mask; + bool user_supplied_mask = false; Header header_3D (input_images[0]); header_3D.ndim() = 3; - auto opt = get_options("mask"); + auto opt = get_options ("mask"); if (opt.size()) { mask = Image::open (opt[0][0]); + user_supplied_mask = true; } else { - auto summed = Image::scratch (header_3D, "summed tissue compartment image"); + auto summed = Image::scratch (header_3D); for (size_t j = 0; j < input_images.size(); ++j) { for (auto i = Loop (summed, 0, 3) (summed, input_images[j]); i; ++i) summed.value() += input_images[j].value(); + progress++; } - Filter::OptimalThreshold threshold_filter (summed); - mask = Image::scratch (threshold_filter); - threshold_filter (summed, mask); + compute_mask (summed, mask); } size_t num_voxels = 0; for (auto i = Loop (mask) (mask); i; ++i) { if (mask.value()) num_voxels++; } - - // TODO reject outliers - + progress++; if (!num_voxels) throw Exception ("error in automatic mask generation. Mask contains no voxels"); const float normalisation_value = get_option_value ("value", DEFAULT_NORM_VALUE); + const size_t max_iter = get_option_value ("maxiter", DEFAULT_MAXITER_VALUE); - // Initialise bias field to 1 + // Initialise bias field Eigen::MatrixXd bias_field_weights (19, 1); - bias_field_weights.setZero(); - bias_field_weights(0,0) = 1.0; - - // Pre-compute bias field basis functions for all voxels - Transform transform (mask); - uint32_t index = 0; - Eigen::MatrixXd bias_field_basis (num_voxels, 19); - for (auto i = Loop(mask)(mask); i; ++i){ - if (mask.value()) { - Eigen::Vector3 vox (mask.index(0), mask.index(1), mask.index(2)); - Eigen::Vector3 pos = transform.voxel2scanner * vox; - bias_field_basis.row (index++) = basis_function (pos).col(0); - } - } - auto bias_field = Image::scratch (header_3D); for (auto i = Loop(bias_field)(bias_field); i; ++i) bias_field.value() = 1.0; - const size_t max_iter = get_option_value ("maxiter", DEFAULT_MAXITER_VALUE); - - // Iterate until convergence or max iterations performed Eigen::MatrixXd scale_factors (input_images.size(), 1); Eigen::MatrixXd previous_scale_factors (input_images.size(), 1); + size_t iter = 0; + bool converged = false; - { - ProgressBar progress ("normalising tissue compartments and estimating biasfield..."); - - // TODO check for convergence - size_t iter = 0; - bool converged = false; - while (!converged && iter < max_iter) { - Eigen::MatrixXd X (num_voxels, input_images.size()); - Eigen::MatrixXd y (num_voxels, 1); - y.fill (normalisation_value); - index = 0; - for (auto i = Loop (mask) (mask, bias_field); i; ++i) { - if (mask.value()) { -// float bias_field_value = bias_field_basis.row (index).dot (bias_field_weights.col(0)); - for (size_t j = 0; j < input_images.size(); ++j) { - assign_pos_of (mask, 0, 3).to (input_images[j]); - X (index, j) = input_images[j].value() / bias_field.value(); - } - ++index; - } + // Iterate until convergence or max iterations performed + while (!converged && iter < max_iter) { + + DEBUG ("iteration: " + str(iter)); + + // Revaluate mask and reject outliers after 1st iteration + if (iter && !user_supplied_mask) { + auto summed = Image::scratch (header_3D); + for (size_t j = 0; j < input_images.size(); ++j) { + for (auto i = Loop (summed, 0, 3) (summed, input_images[j], bias_field); i; ++i) + summed.value() += scale_factors(j, 0) * input_images[j].value() / bias_field.value(); } - progress++; - scale_factors = X.colPivHouseholderQr().solve(y); - progress++; + compute_mask (summed, mask); - index = 0; - for (auto i = Loop (mask) (mask); i; ++i) { + std::vector summed_values; + for (auto i = Loop (mask) (mask, summed); i; ++i) { + if (mask.value()) + summed_values.push_back (summed.value()); + } + num_voxels = summed_values.size(); + std::sort (summed_values.begin(), summed_values.end()); + float lower_quartile = summed_values[std::round ((float)num_voxels * 0.25)]; + float upper_quartile = summed_values[std::round ((float)num_voxels * 0.75)]; + float upper_outlier_threshold = upper_quartile + 1.5 * (upper_quartile - lower_quartile); + float lower_outlier_threshold = lower_quartile - 1.5 * (upper_quartile - lower_quartile); + + for (auto i = Loop (mask) (mask, summed); i; ++i) { if (mask.value()) { - double sum = 0.0; - for (size_t j = 0; j < input_images.size(); ++j) { - assign_pos_of (mask, 0, 3).to (input_images[j]); - sum += scale_factors(j,0) * input_images[j].value() ; + if (summed.value() < lower_outlier_threshold || summed.value() > upper_outlier_threshold) { + mask.value() = 0; + num_voxels--; } - y(index++, 0) = sum / normalisation_value; } } - progress++; + } + progress++; - bias_field_weights = bias_field_basis.colPivHouseholderQr().solve(y); - //bias_field_weights(0,0) = 1.0; // TODO normalise whole field or only within mask? + // Solve for tissue normalisation scale factors + Eigen::MatrixXd X (num_voxels, input_images.size()); + Eigen::MatrixXd y (num_voxels, 1); + y.fill (normalisation_value); + uint32_t index = 0; + for (auto i = Loop (mask) (mask, bias_field); i; ++i) { + if (mask.value()) { + for (size_t j = 0; j < input_images.size(); ++j) { + assign_pos_of (mask, 0, 3).to (input_images[j]); + X (index, j) = input_images[j].value() / bias_field.value(); + } + ++index; + } + } + progress++; + scale_factors = X.colPivHouseholderQr().solve(y); + progress++; - progress++; - // Normalise the bias field within the mask - double mean = 0.0; - for (auto i = Loop (bias_field) (bias_field, mask); i; ++i) { - if (mask.value()) { - Eigen::Vector3 vox (bias_field.index(0), bias_field.index(1), bias_field.index(2)); - Eigen::Vector3 pos = transform.voxel2scanner * vox; - bias_field.value() = basis_function (pos).col(0).dot (bias_field_weights.col(0)); - mean += bias_field.value(); + // Solve for bias field weights + Transform transform (mask); + Eigen::MatrixXd bias_field_basis (num_voxels, 19); + index = 0; + for (auto i = Loop (mask) (mask); i; ++i) { + if (mask.value()) { + Eigen::Vector3 vox (mask.index(0), mask.index(1), mask.index(2)); + Eigen::Vector3 pos = transform.voxel2scanner * vox; + bias_field_basis.row (index) = basis_function (pos).col(0); + + double sum = 0.0; + for (size_t j = 0; j < input_images.size(); ++j) { + assign_pos_of (mask, 0, 3).to (input_images[j]); + sum += scale_factors(j,0) * input_images[j].value() ; } + y (index++, 0) = sum / normalisation_value; } - progress++; - mean /= num_voxels; - for (auto i = Loop (bias_field) (bias_field, mask); i; ++i) { - if (mask.value()) - bias_field.value() /= mean; - } - iter++; + } + progress++; + bias_field_weights = bias_field_basis.colPivHouseholderQr().solve(y); + progress++; - if (iter) { - Eigen::MatrixXd diff = (previous_scale_factors.array() - scale_factors.array()); - for (size_t i = 0; i < diff.rows(); ++i) - diff(i,0) = std::abs(diff(i,0)); - std::cout << "diff" << diff << std::endl; - } - previous_scale_factors = scale_factors; -// float percent_change = diff.col(0).sum() / previous_scale_factors.col(0).sum(); -// std::cout << "prevous" << previous_scale_factors << std::endl; -// std::cout << "percent change " << percent_change << std::endl; - progress++; + // Normalise the bias field within the mask + double mean = 0.0; + for (auto i = Loop (bias_field) (bias_field, mask); i; ++i) { + Eigen::Vector3 vox (bias_field.index(0), bias_field.index(1), bias_field.index(2)); + Eigen::Vector3 pos = transform.voxel2scanner * vox; + bias_field.value() = basis_function (pos).col(0).dot (bias_field_weights.col(0)); + if (mask.value()) + mean += bias_field.value(); } + progress++; + mean /= num_voxels; + for (auto i = Loop (bias_field) (bias_field, mask); i; ++i) + bias_field.value() /= mean; + + // Check for convergence + if (iter) { + Eigen::MatrixXd diff = previous_scale_factors.array() - scale_factors.array(); + diff = diff.array().abs() / previous_scale_factors.array(); + DEBUG ("percentage change in estimated scale factors: " + str(diff.mean() * 100)); + if (diff.mean() < 0.001) + converged = true; + } + previous_scale_factors = scale_factors; + progress++; + iter++; } - - // Construct entire bias field (including outside the mask) - Image bias_field_output; - opt = get_options("bias"); - if (opt.size()) - bias_field_output = Image::create (opt[0][0], header_3D); - else - bias_field_output = Image::scratch (header_3D); - - for (auto i = Loop (bias_field_output) (bias_field_output); i; ++i) { - Eigen::Vector3 vox (bias_field_output.index(0), bias_field_output.index(1), bias_field_output.index(2)); - Eigen::Vector3 pos = transform.voxel2scanner * vox; - bias_field_output.value() = basis_function (pos).col(0).dot (bias_field_weights.col(0)); + opt = get_options ("bias"); + if (opt.size()) { + auto bias_field_output = Image::create (opt[0][0], header_3D); + threaded_copy (bias_field, bias_field_output); } + progress++; + opt = get_options ("check"); + if (opt.size()) { + auto mask_output = Image::create (opt[0][0], mask); + threaded_copy (mask, mask_output); + } + progress++; // compute mean of all scale factors in the log domain - opt = get_options("independent"); + opt = get_options ("independent"); if (!opt.size()) { float mean = 0.0; for (int i = 0; i < scale_factors.size(); ++i) @@ -284,16 +313,20 @@ void run () scale_factors.fill (mean); } - // output bias corrected and normalised tissue maps - ProgressBar progress ("writing output files", input_images.size()); + uint32_t total_count = 0; + for (size_t i = 0; i < output_headers.size(); ++i) { + uint32_t count = 1; + for (size_t j = 0; j < output_headers[i].ndim(); ++j) + count *= output_headers[i].size(j); + total_count += count; + } for (size_t j = 0; j < output_filenames.size(); ++j) { output_headers[j].keyval()["normalisation_scale_factor"] = str(scale_factors(j, 0)); auto output_image = Image::create (output_filenames[j], output_headers[j]); for (auto i = Loop (output_image) (output_image, input_images[j]); i; ++i) { - assign_pos_of (output_image, 0, 3).to (bias_field_output); - output_image.value() = scale_factors(j, 0) * input_images[j].value() / bias_field_output.value(); + assign_pos_of (output_image, 0, 3).to (bias_field); + output_image.value() = scale_factors(j, 0) * input_images[j].value() / bias_field.value(); } - progress++; } } diff --git a/docs/reference/commands/mtbin.rst b/docs/reference/commands/mtbin.rst index 50d7e6ce86..904767b8e0 100644 --- a/docs/reference/commands/mtbin.rst +++ b/docs/reference/commands/mtbin.rst @@ -15,7 +15,7 @@ Synopsis Description ----------- - +Multi-Tissue Bias field correction and Intensity Normalisation (MTBIN). This script inputs N number of tissue components (e.g. from multi-tissue CSD), and outputs N corrected tissue components. Intensity normalisation is performed by either normalising each tissue type independently with a single global scale factor per tissue or determining a normalise all tissues with the same scale factor (default). Example usage: mtbin wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif. The estimated multiplicative bias field is guaranteed to have a mean of 1 over all voxels within the mask Options ------- @@ -26,6 +26,12 @@ Options - **-bias image** output the estimated bias field +- **-independent** intensity normalise each tissue type independently + +- **-maxiter number** set the maximum number of iterations. Default(100). It will stop before the max iterations if convergence is detected + +- **-check image** check the automatically computed mask + Standard options ^^^^^^^^^^^^^^^^ diff --git a/lib/filter/optimal_threshold.h b/lib/filter/optimal_threshold.h index 3b898c1e50..a16cd6e462 100644 --- a/lib/filter/optimal_threshold.h +++ b/lib/filter/optimal_threshold.h @@ -256,7 +256,7 @@ namespace MR typedef typename InputImageType::value_type input_value_type; input_value_type optimal_threshold = estimate_optimal_threshold (input, mask); - + auto f = [&](decltype(input) in, decltype(output) out) { input_value_type val = in.value(); out.value() = ( std::isfinite (val) && val > optimal_threshold ) ? 1 : 0; From c05dd05c28f1454fd307c158b50856398f1d8a08 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 2 Nov 2016 17:25:23 +1100 Subject: [PATCH 211/723] removed the mtbin script --- scripts/mtbin | 93 --------------------------------------------------- 1 file changed, 93 deletions(-) delete mode 100755 scripts/mtbin diff --git a/scripts/mtbin b/scripts/mtbin deleted file mode 100755 index ca5fd7d689..0000000000 --- a/scripts/mtbin +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python - -import math, os, sys, glob -import lib.app, lib.cmdlineParser - -def abspath(*arg): - return os.path.abspath(os.path.join(*arg)) - -def relpath(*arg): - return os.path.relpath(os.path.join(*arg),lib.app.workingDir) - -from lib.printMessage import printMessage -from lib.errorMessage import errorMessage -from lib.getHeaderInfo import getHeaderInfo -from lib.getUserPath import getUserPath -from lib.runCommand import runCommand - -lib.app.author = 'David Raffelt (david.raffelt@florey.edu.au)' -lib.cmdlineParser.initialise('Multi-Tissue Bias field correction and Intensity Normalisation (MTBIN). This script inputs N number of tissue components ' - '(from multi-tissue CSD), and outputs N corrected tissue components. Intensity normalisation is performed using a single global scale factor for each tissue type. ' - 'Example usage: mtbin wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif') -lib.app.parser.add_argument('input_output', nargs='*', help='list of all input and output tissue compartment files. See example usage in the description. ' - 'Note that any number of tissues can be normalised') -options = lib.app.parser.add_argument_group('Options for the mtbin script') -options.add_argument('-mask', help='define the mask to compute the normalisation within. If not supplied this is estimated automatically') -options.add_argument('-value', default='0.282094', help='specify the value to which the summed tissue compartments will be globally normalised to (Default: sqrt(1/(4*pi) = 0.282)') -options.add_argument('-iter', default='4', help='bias field correction and intensity normalisation is performed iteratively. Specify the number of iterations (Default: 4)') -lib.app.parser.add_argument('-bias', help='Output estimated bias field') - - -lib.app.initialise() - -masking = '' -if lib.app.args.mask: - masking = ' -mask ' + relpath(lib.app.args.mask_dir) + ' ' - -max_iterations = int(lib.app.args.iter) - -if (len(lib.app.args.input_output) % 2): - errorMessage ("The number of input arguments must be even. There must be an output file provided for every input tissue image") - -input_files = [] -output_files = [] -for i in range(0, len(lib.app.args.input_output)): - if i % 2: - lib.app.checkOutputFile(lib.app.args.input_output[i]) - output_files.append(lib.app.args.input_output[i]) - else: - input_files.append(os.path.join(lib.app.workingDir, lib.app.args.input_output[i])) - -lib.app.makeTempDir() -lib.app.gotoTempDir() - -norm_files = ['norm_' + os.path.basename(s) for s in input_files] -bias_corrected_files = ['bias_' + os.path.basename(s) for s in input_files] - -for iteration in range(1, max_iterations + 1): - printMessage('iteration: ' + str(iteration)) - - # normalise to 1 while estimating the bias field for output - if iteration == 1: - runCommand('mtnormalise -force -value 1.0 ' + masking + ' '.join([x for t in zip(input_files, norm_files) for x in t])) - else: - runCommand('mtnormalise -force -value 1.0 ' + masking + ' '.join([x for t in zip(bias_corrected_files, norm_files) for x in t])) - - files_to_sum = [] - for f in norm_files: - sizes = getHeaderInfo(f, 'size').split() - if len(sizes) > 3 and int(sizes[3]) > 1: - dc_file_name = 'dc_' + f - runCommand('mrconvert -force -coord 3 0 ' + f + ' ' + dc_file_name) - files_to_sum.append(dc_file_name) - else: - files_to_sum.append(f) - runCommand('mrmath ' + ' '.join(files_to_sum) + ' sum summed.mif -force') - runCommand('mrmodelfield summed.mif field' + str(iteration) + '.mif -force' + masking) - - for a, b in zip(norm_files, bias_corrected_files): - runCommand('mrcalc -force ' + a + ' field' + str(iteration) + '.mif -divide ' + b) - files_to_normalise = bias_corrected_files - -# Final normalisation to value desired by user -runCommand('mtnormalise -force -value ' + lib.app.args.value + ' ' + masking + ' '.join([x for t in zip(bias_corrected_files, norm_files) for x in t])) - -for a, b in zip(norm_files, output_files): - runCommand('mrconvert ' + a + ' ' + getUserPath(b, True) + lib.app.mrtrixForce) - -# combine all fields from each iteration for output -if lib.app.args.bias: - runCommand('mrmath ' + ' '.join(glob.glob('field*.mif')) + ' product total_field.mif') - runCommand('mrconvert total_field.mif ' + getUserPath(lib.app.args.bias, True) + lib.app.mrtrixForce) - -lib.app.complete() From 7b1e2fa831648154b0cca1d4049795d4689df346 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Mon, 7 Nov 2016 11:21:24 +1100 Subject: [PATCH 212/723] fixelconvert: Perform conversion in either direction --- cmd/fixelconvert.cpp | 122 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 109 insertions(+), 13 deletions(-) diff --git a/cmd/fixelconvert.cpp b/cmd/fixelconvert.cpp index e00d91af59..12ab3cfebf 100644 --- a/cmd/fixelconvert.cpp +++ b/cmd/fixelconvert.cpp @@ -24,6 +24,7 @@ #include "fixel_format/helpers.h" #include "fixel_format/keys.h" +#include "fixel_format/loop.h" #include "sparse/fixel_metric.h" #include "sparse/keys.h" @@ -39,26 +40,34 @@ using Sparse::FixelMetric; void usage () { - AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; + AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) and Robert E. Smith (robert.smith@florey.edu.au)"; DESCRIPTION - + "convert an old format fixel image (*.msf) to the new fixel folder format"; + + "convert between the old format fixel image (*.msf / *.msh) and the new fixel folder format"; ARGUMENTS - + Argument ("fixel_in", "the input fixel file.").type_image_in () - + Argument ("fixel_output", "the output fixel folder.").type_text(); + + Argument ("fixel_in", "the input fixel file / folder.").type_text() + + Argument ("fixel_out", "the output fixel file / folder.").type_text(); OPTIONS - + Option ("name", "assign a different name to the value field output (Default: value). Do not include the file extension.") + + 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 ("nii", "output the index, directions and data file in NifTI format instead of *.mif") + + 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(); - + Option ("size", "also output the 'size' field from the old format"); } -void run () + + +void convert_old2new () { Header header (Header::open (argument[0])); header.keyval().erase (Sparse::name_key); @@ -66,18 +75,16 @@ void run () Sparse::Image input (argument[0]); - std::string file_extension (".mif"); - if (get_options ("nii").size()) - file_extension = ".nii"; + const std::string file_extension = get_options ("nii").size() ? ".nii" : ".mif"; std::string value_name ("value"); auto opt = get_options ("name"); if (opt.size()) value_name = std::string (opt[0][0]); - const bool output_size = get_options ("size").size(); + const bool output_size = get_options ("out_size").size(); - std::string output_fixel_folder = argument[1]; + const std::string output_fixel_folder = argument[1]; FixelFormat::check_fixel_folder (output_fixel_folder, true); uint32_t fixel_count = 0; @@ -127,3 +134,92 @@ void run () } } } + + + +void convert_new2old () +{ + const std::string input_fixel_folder = argument[0]; + 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"); + const std::string size_path = opt.size() ? std::string(opt[0][0]) : ""; + + Header H_index = FixelFormat::find_index_header (input_fixel_folder); + Header H_dirs = FixelFormat::find_directions_header (input_fixel_folder); + std::vector
H_data = FixelFormat::find_data_headers (input_fixel_folder, 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)) + value_index = i; + 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 folder corresponding to -value option"); + + Header H_out (H_index); + H_out.ndim() = 3; + H_out.datatype() = DataType::UInt64; + H_out.datatype().set_byte_order_native(); + H_out.keyval()[Sparse::name_key] = str(typeid(FixelMetric).name()); + H_out.keyval()[Sparse::size_key] = str(sizeof(FixelMetric)); + Sparse::Image out_image (argument[1], H_out); + + auto index_image = H_index.get_image(); + auto dirs_image = H_dirs.get_image(); + auto value_image = H_data[value_index].get_image(); + Image size_image; + 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) { + index_image.index(3) = 0; + const uint32_t num_fixels = index_image.value(); + out_image.value().set_size (num_fixels); + for (auto f = FixelFormat::FixelLoop (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(); + } + Sparse::FixelMetric fixel (dir, value_image.value(), value_image.value()); + if (size_image.valid()) { + assign_pos_of (value_image).to (size_image); + fixel.size = size_image.value(); + } + out_image.value()[f.fixel_index] = fixel; + } + } +} + + + +bool is_old_format (const std::string& path) { + return (Path::has_suffix (path, ".msf") || Path::has_suffix (path, ".msh")); +} + + + +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 (); + } 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 (); + } +} + + + + + From 3f2f0eb772559357cfa08570a5d6163f940d85ef Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 8 Nov 2016 16:04:24 +1100 Subject: [PATCH 213/723] mrview: Vector plot: Sort fixel data files --- lib/fixel_format/helpers.h | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index 0fea995cb4..961924ec12 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -200,19 +200,30 @@ namespace MR FORCE_INLINE std::vector
find_data_headers (const std::string &fixel_folder_path, const Header &index_header, const bool include_directions = false) { check_index_image (index_header); - std::vector
data_headers; - auto dir_walker = Path::Dir (fixel_folder_path); - std::string fname; - while ((fname = dir_walker.read_name ()).size ()) { - auto full_path = Path::join (fixel_folder_path, fname); - Header H; - if (Path::has_suffix (fname, FixelFormat::supported_fixel_formats) && is_data_file (H = Header::open (full_path))) { - if (fixels_match (index_header, H)) { - if (!is_directions_file (H) || include_directions) - data_headers.emplace_back (std::move (H)); - } else { - WARN ("fixel data file (" + fname + ") does not contain the same number of elements as fixels in the index file"); + std::vector file_names; + { + std::string temp; + while ((temp = dir_walker.read_name()).size()) + file_names.push_back (temp); + } + std::sort (file_names.begin(), file_names.end()); + + std::vector
data_headers; + for (auto fname : file_names) { + if (Path::has_suffix (fname, FixelFormat::supported_fixel_formats)) { + try { + auto H = Header::open (Path::join (fixel_folder_path, fname)); + if (is_data_file (H)) { + if (fixels_match (index_header, H)) { + if (!is_directions_file (H) || include_directions) + data_headers.emplace_back (std::move (H)); + } else { + WARN ("fixel data file (" + fname + ") does not contain the same number of elements as fixels in the index file"); + } + } + } catch (...) { + WARN ("unable to open file \"" + fname + "\" as potential fixel data file"); } } } @@ -221,9 +232,9 @@ namespace MR } + FORCE_INLINE Header find_directions_header (const std::string fixel_folder_path) { - bool directions_found (false); Header header; check_fixel_folder (fixel_folder_path); From 4d1c8b6459f25221119bcb0b0082429079ab2039 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 8 Nov 2016 17:19:52 +1100 Subject: [PATCH 214/723] Stats: Fix -permutation option indexing from 1 --- cmd/fixelcfestats.cpp | 2 +- lib/math/stats/permutation.cpp | 13 +++++++++---- lib/math/stats/permutation.h | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index d22109bc6f..885ccdf91e 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -161,7 +161,7 @@ void run() { FixelFormat::copy_index_and_directions_file (input_fixel_folder, output_fixel_folder); { - auto directions_data = FixelFormat::find_directions_header (input_fixel_folder).get_image().with_direct_io(); + auto directions_data = FixelFormat::find_directions_header (input_fixel_folder).get_image().with_direct_io ({+2,+1}); // 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) { diff --git a/lib/math/stats/permutation.cpp b/lib/math/stats/permutation.cpp index 03af5cb300..895cb5f4ab 100644 --- a/lib/math/stats/permutation.cpp +++ b/lib/math/stats/permutation.cpp @@ -100,15 +100,20 @@ namespace MR } - std::vector > load_permutations_file (std::string filename) { + + std::vector > load_permutations_file (const std::string& filename) { std::vector > temp = load_matrix_2D_vector (filename); if (!temp.size()) throw Exception ("no data found in permutations file: " + str(filename)); std::vector > permutations (temp[0].size(), std::vector(temp.size())); - for (std::vector::size_type i = 0; i < temp[0].size(); i++) - for (std::vector::size_type j = 0; j < temp.size(); j++) - permutations[i][j] = temp[j][i]; + for (std::vector::size_type i = 0; i < temp[0].size(); i++) { + for (std::vector::size_type j = 0; j < temp.size(); j++) { + if (!temp[j][i]) + throw Exception ("Pre-defined permutation labelling file \"" + filename + "\" contains zeros; labels should be indexed from one"); + permutations[i][j] = temp[j][i]-1; + } + } return permutations; } diff --git a/lib/math/stats/permutation.h b/lib/math/stats/permutation.h index a1730dbff2..a814ff43b2 100644 --- a/lib/math/stats/permutation.h +++ b/lib/math/stats/permutation.h @@ -49,7 +49,7 @@ namespace MR void statistic2pvalue (const vector_type& perm_dist, const vector_type& stats, vector_type& pvalues); - std::vector > load_permutations_file (std::string filename); + std::vector > load_permutations_file (const std::string& filename); From 2c8508fc14c19e45025cf270f3e8887390210e37 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 9 Nov 2016 13:57:48 +1100 Subject: [PATCH 215/723] fixelconvert: New option -template When converting from old to new format, enable nomination of an existing fixel image in the new format that is to be used as a template for performing the conversion. If the number and directions of fixels in each voxel match, then this will ensure that the output values are created with the appropriate offsets such that they can be used in conjunction with the template fixel image. --- cmd/fixelcfestats.cpp | 8 ++++---- cmd/fixelconvert.cpp | 44 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 885ccdf91e..016b0ed74c 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -216,7 +216,7 @@ void run() { std::vector > permutations_nonstationary; if (opt.size()) { permutations_nonstationary = Math::Stats::Permutation::load_permutations_file (opt[0][0]); - nperms_nonstationary = permutations.size(); + nperms_nonstationary = permutations_nonstationary.size(); if (permutations_nonstationary[0].size() != (size_t)design.rows()) throw Exception ("number of rows in the nonstationary permutations file (" + str(opt[0][0]) + ") does not match number of rows in design matrix"); } @@ -225,7 +225,7 @@ void run() { const matrix_type contrast = load_matrix (argument[3]); if (contrast.cols() != design.cols()) - throw Exception ("the number of contrasts does not equal the number of columns in the design matrix"); + throw Exception ("the number of columns per contrast does not equal the number of columns in the design matrix"); if (contrast.rows() > 1) throw Exception ("only a single contrast vector (defined as a row) is currently supported"); @@ -377,10 +377,10 @@ void run() { if (do_nonstationary_adjustment) { if (permutations_nonstationary.size()) { - Stats::PermTest::PermutationStack permutations (permutations_nonstationary, "precomputing empirical statistic for non-stationarity adjustment..."); + Stats::PermTest::PermutationStack permutations (permutations_nonstationary, "precomputing empirical statistic for non-stationarity adjustment"); Stats::PermTest::precompute_empirical_stat (glm_ttest, cfe_integrator, permutations, empirical_cfe_statistic); } else { - Stats::PermTest::PermutationStack permutations (nperms_nonstationary, design.rows(), "precomputing empirical statistic for non-stationarity adjustment...", false); + Stats::PermTest::PermutationStack permutations (nperms_nonstationary, design.rows(), "precomputing empirical statistic for non-stationarity adjustment", false); Stats::PermTest::precompute_empirical_stat (glm_ttest, cfe_integrator, permutations, empirical_cfe_statistic); } output_header.keyval()["nonstationary adjustment"] = str(true); diff --git a/cmd/fixelconvert.cpp b/cmd/fixelconvert.cpp index 12ab3cfebf..9e3c00cf0f 100644 --- a/cmd/fixelconvert.cpp +++ b/cmd/fixelconvert.cpp @@ -55,6 +55,8 @@ void usage () + 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_text() + 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") @@ -115,15 +117,47 @@ void convert_old2new () if (output_size) size_image = Image::create (Path::join (output_fixel_folder, "size" + file_extension), data_header); - int32_t offset = 0; + Image template_index_image; + Image template_directions_image; + opt = get_options ("template"); + if (opt.size()) { + FixelFormat::check_fixel_folder (opt[0][0]); + template_index_image = FixelFormat::find_index_header (opt[0][0]).get_image(); + check_dimensions (index_image, template_index_image); + template_directions_image = FixelFormat::find_directions_header (opt[0][0]).get_image(); + } + + uint32_t offset = 0; for (auto i = Loop ("converting fixel format", input, 0, 3) (input, index_image); i; ++i) { + const uint32_t num_fixels = input.value().size(); + if (template_index_image.valid()) { + 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"); + template_index_image.index(3) = 1; + offset = template_index_image.value(); + } index_image.index(3) = 0; - index_image.value() = (int32_t)input.value().size(); + index_image.value() = num_fixels; index_image.index(3) = 1; - index_image.value() = offset; - for (size_t f = 0; f != input.value().size(); ++f) { + index_image.value() = num_fixels ? offset : 0; + for (size_t f = 0; f != num_fixels; ++f) { directions_image.index(0) = offset; - directions_image.row(1) = input.value()[f].dir; + for (size_t axis = 0; axis != 3; ++axis) { + directions_image.index(1) = axis; + directions_image.value() = input.value()[f].dir[axis]; + } + if (template_directions_image.valid()) { + template_directions_image.index(0) = offset; + Eigen::Vector3f template_dir; + for (size_t axis = 0; axis != 3; ++axis) { + 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"); + } value_image.index(0) = offset; value_image.value() = input.value()[f].value; if (size_image.valid()) { From 71b72f6a1e17f6902f1c7b72148e3b988c95be6d Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Wed, 9 Nov 2016 12:54:13 +0000 Subject: [PATCH 216/723] signal handling: fixes and cleanup - moved signal list to separate file to be included multiple times with a different macro definition (allows single definition and different uses in different contexts). - fix check for atomic flag in handler - use C++11 atomic semantics throughout, rather than mixing C11 & C++11 on the same atomic variable - simplify message printout using snprintf - use default memory ordering for atomic flag operations since the standard already guarantees that it should be appropriate for setting such a flag, and specifies memory_order_seq_cst as the default anyway. --- lib/signal_handler.cpp | 193 ++++++----------------------------------- lib/signals.h | 44 ++++++++++ 2 files changed, 72 insertions(+), 165 deletions(-) create mode 100644 lib/signals.h diff --git a/lib/signal_handler.cpp b/lib/signal_handler.cpp index 5800100b00..5252d16aa4 100644 --- a/lib/signal_handler.cpp +++ b/lib/signal_handler.cpp @@ -23,121 +23,34 @@ #include "file/path.h" #ifdef MRTRIX_WINDOWS -#define STDERR_FILENO 2 +# define STDERR_FILENO 2 #endif + namespace MR { - - std::vector SignalHandler::data; std::atomic_flag SignalHandler::flag = ATOMIC_FLAG_INIT; - - SignalHandler::SignalHandler() { #ifdef MRTRIX_WINDOWS // Use signal() rather than sigaction() for Windows, as the latter is not supported - // Set the handler for a priori known signals only -#ifdef SIGALRM - signal (SIGALRM, handler); -#endif -#ifdef SIGBUS - signal (SIGBUS, handler); -#endif -#ifdef SIGFPE - signal (SIGFPE, handler); -#endif -#ifdef SIGHUP - signal (SIGHUP, handler); -#endif -#ifdef SIGILL // Note: Not generated under Windows - signal (SIGILL, handler); -#endif -#ifdef SIGINT // Note: Not supported for any Win32 application - signal (SIGINT, handler); -#endif -#ifdef SIGPIPE - signal (SIGPIPE, handler); -#endif -#ifdef SIGPWR - signal (SIGPWR, handler); -#endif -#ifdef SIGQUIT - signal (SIGQUIT, handler); -#endif -#ifdef SIGSEGV - signal (SIGSEGV, handler); -#endif -#ifdef SIGSYS - signal (SIGSYS, handler); -#endif -#ifdef SIGTERM // Note: Not generated under Windows - signal (SIGTERM, handler); -#endif -#ifdef SIGXCPU - signal (SIGXCPU, handler); -#endif -#ifdef SIGXFSZ - signal (SIGXFSZ, handler); -#endif - +# define __SIGNAL(SIG,MSG) signal (SIG, handler) #else - // Construct the signal structure struct sigaction act; act.sa_handler = &handler; // Since we're _Exit()-ing for any of these signals, block them all sigfillset (&act.sa_mask); act.sa_flags = 0; - - // Set the handler for a priori known signals only -#ifdef SIGALRM - sigaction (SIGALRM, &act, nullptr); -#endif -#ifdef SIGBUS - sigaction (SIGBUS, &act, nullptr); -#endif -#ifdef SIGFPE - sigaction (SIGFPE, &act, nullptr); -#endif -#ifdef SIGHUP - sigaction (SIGHUP, &act, nullptr); -#endif -#ifdef SIGILL - sigaction (SIGILL, &act, nullptr); -#endif -#ifdef SIGINT - sigaction (SIGINT, &act, nullptr); -#endif -#ifdef SIGPIPE - sigaction (SIGPIPE, &act, nullptr); -#endif -#ifdef SIGPWR - sigaction (SIGPWR, &act, nullptr); -#endif -#ifdef SIGQUIT - sigaction (SIGQUIT, &act, nullptr); -#endif -#ifdef SIGSEGV - sigaction (SIGSEGV, &act, nullptr); -#endif -#ifdef SIGSYS - sigaction (SIGSYS, &act, nullptr); -#endif -#ifdef SIGTERM - sigaction (SIGTERM, &act, nullptr); -#endif -#ifdef SIGXCPU - sigaction (SIGXCPU, &act, nullptr); -#endif -#ifdef SIGXFSZ - sigaction (SIGXFSZ, &act, nullptr); +# define __SIGNAL(SIG,MSG) sigaction (SIG, &act, nullptr) #endif -#endif +#include "signals.h" + +#undef __SIGNAL } @@ -145,14 +58,14 @@ namespace MR void SignalHandler::operator+= (const std::string& s) { - while (!flag.test_and_set(std::memory_order_seq_cst)); + while (!flag.test_and_set()); data.push_back (s); - flag.clear (std::memory_order_seq_cst); + flag.clear(); } void SignalHandler::operator-= (const std::string& s) { - while (!flag.test_and_set (std::memory_order_seq_cst)); + while (!flag.test_and_set()); auto i = data.begin(); while (i != data.end()) { if (*i == s) @@ -160,7 +73,7 @@ namespace MR else ++i; } - flag.clear (std::memory_order_seq_cst); + flag.clear(); } @@ -168,84 +81,34 @@ namespace MR void SignalHandler::handler (int i) noexcept { - // Only show one signal error message if multi-threading - if (atomic_flag_test_and_set_explicit (&flag, std::memory_order_seq_cst)) { + // Only process this once if using multi-threading: + if (!flag.test_and_set()) { // Try to do a tempfile cleanup before printing the error, since the latter's not guaranteed to work... - for (auto i : data) { - // Don't use File::unlink: may throw an exception + // Don't use File::unlink: may throw an exception + for (const auto& i : data) ::unlink (i.c_str()); - } - // Don't use std::cerr << here: Use basic C string-handling functions and a write() call to STDERR_FILENO - -#define PRINT_SIGNAL(sig,msg) \ - case sig: \ - strcat (s, #sig); \ - strcat (s, " ("); \ - snprintf (i_string, 2, "%d", sig); \ - strcat (s, i_string); \ - strcat (s, ")] "); \ - strcat (s, msg); \ - break; + const char* sig = nullptr; + const char* msg = nullptr; + switch (i) { - char s[128] = "\n"; - strcat (s, App::NAME.c_str()); - strcat (s, ": [SYSTEM FATAL CODE: "); - char i_string[3]; // Max 2 digits, plus null-terminator +#define __SIGNAL(SIG,MSG) case SIG: sig = #SIG; msg = MSG; break; +#include "signals.h" - // Don't attempt to use any terminal colouring - switch (i) { -#ifdef SIGALRM - PRINT_SIGNAL(SIGALRM,"Timer expiration"); -#endif -#ifdef SIGBUS - PRINT_SIGNAL(SIGBUS,"Bus error: Accessing invalid address (out of storage space?)"); -#endif -#ifdef SIGFPE - PRINT_SIGNAL(SIGFPE,"Floating-point arithmetic exception"); -#endif -#ifdef SIGHUP - PRINT_SIGNAL(SIGHUP,"Disconnection of terminal"); -#endif -#ifdef SIGILL - PRINT_SIGNAL(SIGILL,"Illegal instruction (corrupt binary command file?)"); -#endif -#ifdef SIGINT - PRINT_SIGNAL(SIGINT,"Program manually interrupted by terminal"); -#endif -#ifdef SIGPIPE - PRINT_SIGNAL(SIGPIPE,"Nothing on receiving end of pipe"); -#endif -#ifdef SIGPWR - PRINT_SIGNAL(SIGPWR,"Power failure restart"); -#endif -#ifdef SIGQUIT - PRINT_SIGNAL(SIGQUIT,"Received terminal quit signal"); -#endif -#ifdef SIGSEGV - PRINT_SIGNAL(SIGSEGV,"Segmentation fault: Invalid memory reference"); -#endif -#ifdef SIGSYS - PRINT_SIGNAL(SIGSYS,"Bad system call"); -#endif -#ifdef SIGTERM - PRINT_SIGNAL(SIGTERM,"Terminated by kill command"); -#endif -#ifdef SIGXCPU - PRINT_SIGNAL(SIGXCPU,"CPU time limit exceeded"); -#endif -#ifdef SIGXFSZ - PRINT_SIGNAL(SIGXFSZ,"File size limit exceeded"); -#endif default: - strcat (s, "?] Unknown fatal system signal"); + sig = "UNKNOWN"; + msg = "Unknown fatal system signal"; break; } - strcat (s, "\n"); - write (STDERR_FILENO, s, sizeof(s) - 1); + // Don't use std::cerr << here: Use basic C string-handling functions and a write() call to STDERR_FILENO + // Don't attempt to use any terminal colouring + char str[256]; + str[255] = '\0'; + snprintf (str, 255, "\n%s: [SYSTEM FATAL CODE: %s (%d)] %s\n", App::NAME.c_str(), sig, i, msg); + write (STDERR_FILENO, str, strnlen(str,256)); std::_Exit (i); } } diff --git a/lib/signals.h b/lib/signals.h new file mode 100644 index 0000000000..fed0557a03 --- /dev/null +++ b/lib/signals.h @@ -0,0 +1,44 @@ +// Set the handler for a priori known signals only +#ifdef SIGALRM + __SIGNAL (SIGALRM, "Timer expiration"); +#endif +#ifdef SIGBUS + __SIGNAL (SIGBUS, "Bus error: Accessing invalid address (out of storage space?)"); +#endif +#ifdef SIGFPE + __SIGNAL (SIGFPE, "Floating-point arithmetic exception"); +#endif +#ifdef SIGHUP + __SIGNAL (SIGHUP, "Disconnection of terminal"); +#endif +#ifdef SIGILL // Note: Not generated under Windows + __SIGNAL (SIGILL, "Illegal instruction (corrupt binary command file?)"); +#endif +#ifdef SIGINT // Note: Not supported for any Win32 application + __SIGNAL (SIGINT, "Program manually interrupted by terminal"); +#endif +#ifdef SIGPIPE + __SIGNAL (SIGPIPE, "Nothing on receiving end of pipe"); +#endif +#ifdef SIGPWR + __SIGNAL (SIGPWR, "Power failure restart"); +#endif +#ifdef SIGQUIT + __SIGNAL (SIGQUIT, "Received terminal quit signal"); +#endif +#ifdef SIGSEGV + __SIGNAL (SIGSEGV, "Segmentation fault: Invalid memory access"); +#endif +#ifdef SIGSYS + __SIGNAL (SIGSYS, "Bad system call"); +#endif +#ifdef SIGTERM // Note: Not generated under Windows + __SIGNAL (SIGTERM, "Terminated by kill command"); +#endif +#ifdef SIGXCPU + __SIGNAL (SIGXCPU, "CPU time limit exceeded"); +#endif +#ifdef SIGXFSZ + __SIGNAL (SIGXFSZ, "File size limit exceeded"); +#endif + From 9b542c3bda1ef5f72428a8dae383ccf9e065b30b Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Mon, 14 Nov 2016 10:22:13 +1100 Subject: [PATCH 217/723] allow indexing in permutations file to start from 0 or 1 --- lib/math/stats/permutation.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/math/stats/permutation.cpp b/lib/math/stats/permutation.cpp index 895cb5f4ab..bb9506578c 100644 --- a/lib/math/stats/permutation.cpp +++ b/lib/math/stats/permutation.cpp @@ -106,12 +106,16 @@ namespace MR if (!temp.size()) throw Exception ("no data found in permutations file: " + str(filename)); + size_t min_value = *std::min_element (std::begin (temp[0]), std::end (temp[0])); + if (min_value > 1) + throw Exception ("indices for relabelling in permutations file must start from either 0 or 1"); + std::vector > permutations (temp[0].size(), std::vector(temp.size())); for (std::vector::size_type i = 0; i < temp[0].size(); i++) { for (std::vector::size_type j = 0; j < temp.size(); j++) { if (!temp[j][i]) throw Exception ("Pre-defined permutation labelling file \"" + filename + "\" contains zeros; labels should be indexed from one"); - permutations[i][j] = temp[j][i]-1; + permutations[i][j] = temp[j][i] - min_value; } } return permutations; From c37250362772d57f0297eacdab039884d145132e Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Mon, 14 Nov 2016 10:24:10 +1100 Subject: [PATCH 218/723] notfound script: change max directory depth to 1 --- scripts/notfound | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/notfound b/scripts/notfound index e1792694f8..331eeec32a 100755 --- a/scripts/notfound +++ b/scripts/notfound @@ -19,5 +19,5 @@ exit 1 fi -find ${1} -mindepth 1 -maxdepth 2 \( -type l -o -type d \) '!' -exec test -e "{}/${2}" ';' -print +find ${1} -mindepth 1 -maxdepth 1 \( -type l -o -type d \) '!' -exec test -e "{}/${2}" ';' -print From 4b8c918bbe3267d8d9c20254f0f847a44230b74d Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Mon, 14 Nov 2016 11:42:27 +1100 Subject: [PATCH 219/723] mtbin: few more tweaks --- cmd/mtbin.cpp | 107 +++++++++++++---------- docs/reference/commands/fixelconvert.rst | 26 ++++-- docs/reference/commands/mtbin.rst | 2 +- docs/reference/scripts_list.rst | 2 - 4 files changed, 81 insertions(+), 56 deletions(-) diff --git a/cmd/mtbin.cpp b/cmd/mtbin.cpp index 87b516042a..7cf5f77ccd 100644 --- a/cmd/mtbin.cpp +++ b/cmd/mtbin.cpp @@ -51,7 +51,7 @@ void usage () + Argument ("image").type_image_in () + Option ("value", "specify the value to which the summed tissue compartments will be to " - "(Default: sqrt(1/(4*pi) = " + str(DEFAULT_NORM_VALUE, 3) + ")") + "(Default: sqrt(1/(4*pi)) = " + str(DEFAULT_NORM_VALUE, 3) + ")") + Argument ("number").type_float () + Option ("bias", "output the estimated bias field") @@ -67,12 +67,14 @@ void usage () + Argument ("image").type_image_out (); } +const int n_basis_vecs (20); + FORCE_INLINE Eigen::MatrixXd basis_function (const Eigen::Vector3 pos) { - float x = (float)pos[0]; - float y = (float)pos[1]; - float z = (float)pos[2]; - Eigen::MatrixXd basis(19, 1); + double x = pos[0]; + double y = pos[1]; + double z = pos[2]; + Eigen::MatrixXd basis(n_basis_vecs, 1); basis(0) = 1.0; basis(1) = x; basis(2) = y; @@ -92,6 +94,7 @@ FORCE_INLINE Eigen::MatrixXd basis_function (const Eigen::Vector3 pos) { basis(16) = x * x * x; basis(17) = y * y * y; basis(18) = z * z * z; + basis(19) = x * y * z; return basis; } @@ -133,7 +136,8 @@ void run () if (i) check_dimensions (input_images[0], input_images[i / 2], 0, 3); - // TODO check for output and not -force + if (Path::exists (argument[i + 1]) && !App::overwrite_files) + throw Exception ("output file \"" + argument[i] + "\" already exists (use -force option to force overwrite)"); // we can't create the image yet if we want to put the scale factor into the output header output_headers.emplace_back (Header::open (argument[i])); @@ -172,53 +176,20 @@ void run () const size_t max_iter = get_option_value ("maxiter", DEFAULT_MAXITER_VALUE); // Initialise bias field - Eigen::MatrixXd bias_field_weights (19, 1); + Eigen::MatrixXd bias_field_weights (n_basis_vecs, 1); auto bias_field = Image::scratch (header_3D); for (auto i = Loop(bias_field)(bias_field); i; ++i) bias_field.value() = 1.0; Eigen::MatrixXd scale_factors (input_images.size(), 1); Eigen::MatrixXd previous_scale_factors (input_images.size(), 1); - size_t iter = 0; + size_t iter = 1; bool converged = false; // Iterate until convergence or max iterations performed while (!converged && iter < max_iter) { - DEBUG ("iteration: " + str(iter)); - - // Revaluate mask and reject outliers after 1st iteration - if (iter && !user_supplied_mask) { - auto summed = Image::scratch (header_3D); - for (size_t j = 0; j < input_images.size(); ++j) { - for (auto i = Loop (summed, 0, 3) (summed, input_images[j], bias_field); i; ++i) - summed.value() += scale_factors(j, 0) * input_images[j].value() / bias_field.value(); - } - compute_mask (summed, mask); - - std::vector summed_values; - for (auto i = Loop (mask) (mask, summed); i; ++i) { - if (mask.value()) - summed_values.push_back (summed.value()); - } - num_voxels = summed_values.size(); - std::sort (summed_values.begin(), summed_values.end()); - float lower_quartile = summed_values[std::round ((float)num_voxels * 0.25)]; - float upper_quartile = summed_values[std::round ((float)num_voxels * 0.75)]; - float upper_outlier_threshold = upper_quartile + 1.5 * (upper_quartile - lower_quartile); - float lower_outlier_threshold = lower_quartile - 1.5 * (upper_quartile - lower_quartile); - - for (auto i = Loop (mask) (mask, summed); i; ++i) { - if (mask.value()) { - if (summed.value() < lower_outlier_threshold || summed.value() > upper_outlier_threshold) { - mask.value() = 0; - num_voxels--; - } - } - } - } - progress++; - + INFO ("iteration: " + str(iter)); // Solve for tissue normalisation scale factors Eigen::MatrixXd X (num_voxels, input_images.size()); Eigen::MatrixXd y (num_voxels, 1); @@ -237,10 +208,12 @@ void run () scale_factors = X.colPivHouseholderQr().solve(y); progress++; + INFO ("scale factors: " + str(scale_factors.transpose())); + // Solve for bias field weights Transform transform (mask); - Eigen::MatrixXd bias_field_basis (num_voxels, 19); + Eigen::MatrixXd bias_field_basis (num_voxels, n_basis_vecs); index = 0; for (auto i = Loop (mask) (mask); i; ++i) { if (mask.value()) { @@ -275,15 +248,57 @@ void run () for (auto i = Loop (bias_field) (bias_field, mask); i; ++i) bias_field.value() /= mean; + progress++; + // Check for convergence - if (iter) { - Eigen::MatrixXd diff = previous_scale_factors.array() - scale_factors.array(); + Eigen::MatrixXd diff; + if (iter > 1) { + diff = previous_scale_factors.array() - scale_factors.array(); diff = diff.array().abs() / previous_scale_factors.array(); - DEBUG ("percentage change in estimated scale factors: " + str(diff.mean() * 100)); + INFO ("percentage change in estimated scale factors: " + str(diff.mean() * 100)); if (diff.mean() < 0.001) converged = true; } + + // Revaluate mask + if (!converged && !user_supplied_mask) { + auto summed = Image::scratch (header_3D); + for (size_t j = 0; j < input_images.size(); ++j) { + for (auto i = Loop (summed, 0, 3) (summed, input_images[j], bias_field); i; ++i) + summed.value() += scale_factors(j, 0) * input_images[j].value() / bias_field.value(); + } + compute_mask (summed, mask); + std::vector summed_values; + for (auto i = Loop (mask) (mask, summed); i; ++i) { + if (mask.value()) + summed_values.push_back (summed.value()); + } + num_voxels = summed_values.size(); + + // Reject outliers after a few iterations once the summed image is largely corrected for the bias field + if (iter > 2) { + INFO ("rejecting outliers"); + std::sort (summed_values.begin(), summed_values.end()); + float lower_quartile = summed_values[std::round ((float)num_voxels * 0.25)]; + float upper_quartile = summed_values[std::round ((float)num_voxels * 0.75)]; + float upper_outlier_threshold = upper_quartile + 1.6 * (upper_quartile - lower_quartile); + float lower_outlier_threshold = lower_quartile - 1.6 * (upper_quartile - lower_quartile); + + for (auto i = Loop (mask) (mask, summed); i; ++i) { + if (mask.value()) { + if (summed.value() < lower_outlier_threshold || summed.value() > upper_outlier_threshold) { + mask.value() = 0; + num_voxels--; + } + } + } + } + if (log_level >= 3) + display (mask); + } + previous_scale_factors = scale_factors; + progress++; iter++; } diff --git a/docs/reference/commands/fixelconvert.rst b/docs/reference/commands/fixelconvert.rst index d8f4a33e7a..fe8a8f9abb 100644 --- a/docs/reference/commands/fixelconvert.rst +++ b/docs/reference/commands/fixelconvert.rst @@ -8,24 +8,36 @@ Synopsis :: - fixelconvert [ options ] fixel_in fixel_output + fixelconvert [ options ] fixel_in fixel_out -- *fixel_in*: the input fixel file. -- *fixel_output*: the output fixel folder. +- *fixel_in*: the input fixel file / folder. +- *fixel_out*: the output fixel file / folder. Description ----------- -convert an old format fixel image (*.msf) to the new fixel folder format +convert between the old format fixel image (*.msf / *.msh) and the new fixel folder format Options ------- +Options for converting from old to new format +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + - **-name string** assign a different name to the value field output (Default: value). Do not include the file extension. -- **-nii** output the index, directions and data file in NifTI format instead of *.mif +- **-nii** output the index, directions and data file in NIfTI format instead of *.mif + +- **-out_size** also output the 'size' field from the old format + +- **-template path** specify an existing fixel directory (in the new format) to which the new output should conform + +Options for converting from new to old format +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **-value path** nominate the data file to import to the 'value' field in the old format -- **-size** also output the 'size' field from the old format +- **-in_size path** import data for the 'size' field in the old format Standard options ^^^^^^^^^^^^^^^^ @@ -50,7 +62,7 @@ Standard options -**Author:** David Raffelt (david.raffelt@florey.edu.au) +**Author:** David Raffelt (david.raffelt@florey.edu.au) and Robert E. Smith (robert.smith@florey.edu.au) **Copyright:** Copyright (c) 2008-2016 the MRtrix3 contributors diff --git a/docs/reference/commands/mtbin.rst b/docs/reference/commands/mtbin.rst index 904767b8e0..9a0ae669ae 100644 --- a/docs/reference/commands/mtbin.rst +++ b/docs/reference/commands/mtbin.rst @@ -22,7 +22,7 @@ Options - **-mask image** define the mask to compute the normalisation within. If not supplied this is estimated automatically -- **-value number** specify the value to which the summed tissue compartments will be to (Default: sqrt(1/(4*pi) = 0.282) +- **-value number** specify the value to which the summed tissue compartments will be to (Default: sqrt(1/(4*pi)) = 0.282) - **-bias image** output the estimated bias field diff --git a/docs/reference/scripts_list.rst b/docs/reference/scripts_list.rst index 26548fb0fd..bebe3131c3 100644 --- a/docs/reference/scripts_list.rst +++ b/docs/reference/scripts_list.rst @@ -21,6 +21,4 @@ Python scripts provided with MRtrix3 scripts/labelsgmfix - scripts/mtbin - scripts/population_template From 9abb6ecf556b72b9fd7ca5350a452dc4eaebab73 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Tue, 15 Nov 2016 13:41:04 +1100 Subject: [PATCH 220/723] Improved output checking in fixel commands, added image_diff containing common functions for checking image differences, changed naming of folder to directory in fixel commands --- .gitignore | 1 - cmd/fixel2sh.cpp | 4 +- cmd/fixel2tsf.cpp | 6 +- cmd/fixel2voxel.cpp | 4 +- cmd/fixelcfestats.cpp | 46 +++--- cmd/fixelconvert.cpp | 30 ++-- cmd/fixelcorrespondence.cpp | 22 +-- cmd/fixelcrop.cpp | 20 +-- cmd/fixelreorient.cpp | 28 ++-- cmd/fod2fixel.cpp | 22 +-- cmd/voxel2fixel.cpp | 20 +-- cmd/warp2metric.cpp | 24 ++-- docs/reference/commands/fixel2tsf.rst | 2 +- docs/reference/commands/fixelcfestats.rst | 8 +- docs/reference/commands/fixelconvert.rst | 6 +- .../commands/fixelcorrespondence.rst | 10 +- docs/reference/commands/fixelcrop.rst | 6 +- docs/reference/commands/fixelreorient.rst | 4 +- docs/reference/commands/fod2fixel.rst | 4 +- docs/reference/commands/voxel2fixel.rst | 6 +- docs/reference/commands/warp2metric.rst | 2 +- lib/fixel_format/helpers.h | 123 +++++++++------- lib/image_diff.h | 131 ++++++++++++++++++ lib/image_helpers.h | 21 +++ testing/data | 2 +- testing/src/diff_images.h | 70 ++++++++++ testing/tests/voxel2fixel | 2 +- 27 files changed, 437 insertions(+), 187 deletions(-) create mode 100644 lib/image_diff.h create mode 100644 testing/src/diff_images.h diff --git a/.gitignore b/.gitignore index 70188b9584..d3aaf67511 100644 --- a/.gitignore +++ b/.gitignore @@ -45,5 +45,4 @@ icons.cpp testing.log testing/build.log testing/release/ -testing/src/ .__tmp.log diff --git a/cmd/fixel2sh.cpp b/cmd/fixel2sh.cpp index ef4a70d4d7..4b91207473 100644 --- a/cmd/fixel2sh.cpp +++ b/cmd/fixel2sh.cpp @@ -55,9 +55,9 @@ void run () { auto in_data_image = FixelFormat::open_fixel_data_file (argument[0]); - Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_folder (argument[0])); + Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_directory (argument[0])); auto in_index_image =in_index_header.get_image(); - auto in_directions_image = FixelFormat::find_directions_header (FixelFormat::get_fixel_folder (argument[0])).get_image().with_direct_io(); + auto in_directions_image = FixelFormat::find_directions_header (FixelFormat::get_fixel_directory (argument[0])).get_image().with_direct_io(); size_t lmax = 8; auto opt = get_options ("lmax"); diff --git a/cmd/fixel2tsf.cpp b/cmd/fixel2tsf.cpp index e36a4c4f58..7d06334cfb 100644 --- a/cmd/fixel2tsf.cpp +++ b/cmd/fixel2tsf.cpp @@ -47,7 +47,7 @@ void usage () "This is useful for visualising all brain fixels (e.g. the output from fixelcfestats) in 3D."; ARGUMENTS - + Argument ("fixel_in", "the input fixel data file (within the fixel folder)").type_image_in () + + 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 (); @@ -70,9 +70,9 @@ void run () 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 = FixelFormat::find_index_header (FixelFormat::get_fixel_folder (argument[0])); + Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_directory (argument[0])); auto in_index_image = in_index_header.get_image(); - auto in_directions_image = FixelFormat::find_directions_header (FixelFormat::get_fixel_folder (argument[0])).get_image().with_direct_io(); + auto in_directions_image = FixelFormat::find_directions_header (FixelFormat::get_fixel_directory (argument[0])).get_image().with_direct_io(); DWI::Tractography::Properties properties; DWI::Tractography::Reader reader (argument[1], properties); diff --git a/cmd/fixel2voxel.cpp b/cmd/fixel2voxel.cpp index d8ea8154eb..1aa3c77959 100644 --- a/cmd/fixel2voxel.cpp +++ b/cmd/fixel2voxel.cpp @@ -416,7 +416,7 @@ void run () 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)"); - Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_folder (argument[0])); + Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_directory (argument[0])); auto in_index_image = in_index_header.get_image(); Image in_directions; @@ -446,7 +446,7 @@ void run () if (op == 10 || op == 11 || op == 13) // dec or split_dir in_directions = FixelFormat::find_directions_header ( - FixelFormat::get_fixel_folder (in_data.name())).get_image().with_direct_io(); + FixelFormat::get_fixel_directory (in_data.name())).get_image().with_direct_io(); Image in_vol; auto opt = get_options ("weighted"); diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 016b0ed74c..db22491216 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -67,10 +67,10 @@ void usage () "NeuroImage, 2011, 54(3), 2006-19\n" ; ARGUMENTS - + Argument ("in_fixel_folder", "the fixel folder containing the data files for each subject (after obtaining fixel correspondence").type_file_in () + + Argument ("in_fixel_directory", "the fixel directory containing the data files for each subject (after obtaining fixel correspondence").type_file_in () + Argument ("subjects", "a text file listing the subject identifiers (one per line). This should correspond with the filenames " - "in the fixel folder (including the file extension), and be listed in the same order as the rows of the design matrix.").type_image_in () + "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 ("design", "the design matrix. Note that a column of 1's will need to be added for correlations.").type_file_in () @@ -78,7 +78,7 @@ void usage () + Argument ("tracks", "the tracks used to determine fixel-fixel connectivity").type_tracks_in () - + Argument ("out_fixel_folder", "the output folder where results will be saved. Will be created if it does not exist").type_text(); + + Argument ("out_fixel_directory", "the output directory where results will be saved. Will be created if it does not exist").type_text(); OPTIONS @@ -147,8 +147,8 @@ void run() { const value_type angular_threshold = get_option_value ("angle", DEFAULT_ANGLE_THRESHOLD); - const std::string input_fixel_folder = argument[0]; - Header index_header = FixelFormat::find_index_header (input_fixel_folder); + const std::string input_fixel_directory = argument[0]; + Header index_header = FixelFormat::find_index_header (input_fixel_directory); auto index_image = index_header.get_image(); const uint32_t num_fixels = FixelFormat::get_number_of_fixels (index_header); @@ -157,11 +157,11 @@ void run() { std::vector positions (num_fixels); std::vector directions (num_fixels); - const std::string output_fixel_folder = argument[5]; - FixelFormat::copy_index_and_directions_file (input_fixel_folder, output_fixel_folder); + const std::string output_fixel_directory = argument[5]; + FixelFormat::copy_index_and_directions_file (input_fixel_directory, output_fixel_directory); { - auto directions_data = FixelFormat::find_directions_header (input_fixel_folder).get_image().with_direct_io ({+2,+1}); + auto directions_data = FixelFormat::find_directions_header (input_fixel_directory).get_image().with_direct_io ({+2,+1}); // 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) { @@ -183,7 +183,7 @@ void run() { std::ifstream ifs (argument[1].c_str()); std::string temp; while (getline (ifs, temp)) { - std::string filename (Path::join (input_fixel_folder, temp)); + std::string filename (Path::join (input_fixel_directory, temp)); size_t p = filename.find_last_not_of(" \t"); if (std::string::npos != p) filename.erase(p+1); @@ -357,15 +357,15 @@ void run() { auto temp = Math::Stats::GLM::solve_betas (data, design); for (ssize_t i = 0; i < contrast.cols(); ++i) { - write_fixel_output (Path::join (output_fixel_folder, "beta" + str(i) + ".mif"), temp.row(i), output_header); + write_fixel_output (Path::join (output_fixel_directory, "beta" + str(i) + ".mif"), temp.row(i), output_header); ++progress; } temp = Math::Stats::GLM::abs_effect_size (data, design, contrast); ++progress; - write_fixel_output (Path::join (output_fixel_folder, "abs_effect.mif"), temp.row(0), output_header); ++progress; + write_fixel_output (Path::join (output_fixel_directory, "abs_effect.mif"), temp.row(0), output_header); ++progress; temp = Math::Stats::GLM::std_effect_size (data, design, contrast); ++progress; - write_fixel_output (Path::join (output_fixel_folder, "std_effect.mif"), temp.row(0), output_header); ++progress; + write_fixel_output (Path::join (output_fixel_directory, "std_effect.mif"), temp.row(0), output_header); ++progress; temp = Math::Stats::GLM::stdev (data, design); ++progress; - write_fixel_output (Path::join (output_fixel_folder, "std_dev.mif"), temp.row(0), output_header); + write_fixel_output (Path::join (output_fixel_directory, "std_dev.mif"), temp.row(0), output_header); } Math::Stats::GLMTTest glm_ttest (data, design, contrast); @@ -384,7 +384,7 @@ void run() { Stats::PermTest::precompute_empirical_stat (glm_ttest, cfe_integrator, permutations, empirical_cfe_statistic); } output_header.keyval()["nonstationary adjustment"] = str(true); - write_fixel_output (Path::join (output_fixel_folder, "cfe_empirical.mif"), empirical_cfe_statistic, output_header); + write_fixel_output (Path::join (output_fixel_directory, "cfe_empirical.mif"), empirical_cfe_statistic, output_header); } else { output_header.keyval()["nonstationary adjustment"] = str(false); } @@ -398,10 +398,10 @@ void run() { Stats::PermTest::precompute_default_permutation (glm_ttest, cfe_integrator, empirical_cfe_statistic, cfe_output, cfe_output_neg, tvalue_output); - write_fixel_output (Path::join (output_fixel_folder, "cfe.mif"), cfe_output, output_header); - write_fixel_output (Path::join (output_fixel_folder, "tvalue.mif"), tvalue_output, output_header); + write_fixel_output (Path::join (output_fixel_directory, "cfe.mif"), cfe_output, output_header); + write_fixel_output (Path::join (output_fixel_directory, "tvalue.mif"), tvalue_output, output_header); if (compute_negative_contrast) - write_fixel_output (Path::join (output_fixel_folder, "cfe_neg.mif"), *cfe_output_neg, output_header); + write_fixel_output (Path::join (output_fixel_directory, "cfe_neg.mif"), *cfe_output_neg, output_header); // Perform permutation testing opt = get_options ("notest"); @@ -429,19 +429,19 @@ void run() { } ProgressBar progress ("outputting final results"); - save_matrix (perm_distribution, Path::join (output_fixel_folder, "perm_dist.txt")); ++progress; + save_matrix (perm_distribution, Path::join (output_fixel_directory, "perm_dist.txt")); ++progress; vector_type pvalue_output (num_fixels); Math::Stats::Permutation::statistic2pvalue (perm_distribution, cfe_output, pvalue_output); ++progress; - write_fixel_output (Path::join (output_fixel_folder, "fwe_pvalue.mif"), pvalue_output, output_header); ++progress; - write_fixel_output (Path::join (output_fixel_folder, "uncorrected_pvalue.mif"), uncorrected_pvalues, output_header); ++progress; + write_fixel_output (Path::join (output_fixel_directory, "fwe_pvalue.mif"), pvalue_output, output_header); ++progress; + write_fixel_output (Path::join (output_fixel_directory, "uncorrected_pvalue.mif"), uncorrected_pvalues, output_header); ++progress; if (compute_negative_contrast) { - save_matrix (*perm_distribution_neg, Path::join (output_fixel_folder, "perm_dist_neg.txt")); ++progress; + save_matrix (*perm_distribution_neg, Path::join (output_fixel_directory, "perm_dist_neg.txt")); ++progress; vector_type pvalue_output_neg (num_fixels); Math::Stats::Permutation::statistic2pvalue (*perm_distribution_neg, *cfe_output_neg, pvalue_output_neg); ++progress; - write_fixel_output (Path::join (output_fixel_folder, "fwe_pvalue_neg.mif"), pvalue_output_neg, output_header); ++progress; - write_fixel_output (Path::join (output_fixel_folder, "uncorrected_pvalue_neg.mif"), *uncorrected_pvalues_neg, output_header); + write_fixel_output (Path::join (output_fixel_directory, "fwe_pvalue_neg.mif"), pvalue_output_neg, output_header); ++progress; + write_fixel_output (Path::join (output_fixel_directory, "uncorrected_pvalue_neg.mif"), *uncorrected_pvalues_neg, output_header); } } } diff --git a/cmd/fixelconvert.cpp b/cmd/fixelconvert.cpp index 9e3c00cf0f..4e23179f16 100644 --- a/cmd/fixelconvert.cpp +++ b/cmd/fixelconvert.cpp @@ -43,11 +43,11 @@ void usage () AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) and Robert E. Smith (robert.smith@florey.edu.au)"; DESCRIPTION - + "convert between the old format fixel image (*.msf / *.msh) and the new fixel folder format"; + + "convert between the old format fixel image (*.msf / *.msh) and the new fixel directory format"; ARGUMENTS - + Argument ("fixel_in", "the input fixel file / folder.").type_text() - + Argument ("fixel_out", "the output fixel file / folder.").type_text(); + + Argument ("fixel_in", "the input fixel file / directory.").type_text() + + Argument ("fixel_out", "the output fixel file / directory.").type_text(); OPTIONS + OptionGroup ("Options for converting from old to new format") @@ -86,8 +86,8 @@ void convert_old2new () const bool output_size = get_options ("out_size").size(); - const std::string output_fixel_folder = argument[1]; - FixelFormat::check_fixel_folder (output_fixel_folder, true); + const std::string output_fixel_directory = argument[1]; + FixelFormat::check_fixel_directory (output_fixel_directory, true); uint32_t fixel_count = 0; for (auto i = Loop (input) (input); i; ++i) @@ -110,18 +110,18 @@ void convert_old2new () header.datatype() = DataType::from(); header.datatype().set_byte_order_native(); - auto index_image = Image::create (Path::join (output_fixel_folder, "index" + file_extension), header); - auto directions_image = Image::create (Path::join (output_fixel_folder, "directions" + file_extension), directions_header).with_direct_io(); - auto value_image = Image::create (Path::join (output_fixel_folder, 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_folder, "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"); if (opt.size()) { - FixelFormat::check_fixel_folder (opt[0][0]); + FixelFormat::check_fixel_directory (opt[0][0]); template_index_image = FixelFormat::find_index_header (opt[0][0]).get_image(); check_dimensions (index_image, template_index_image); template_directions_image = FixelFormat::find_directions_header (opt[0][0]).get_image(); @@ -173,7 +173,7 @@ void convert_old2new () void convert_new2old () { - const std::string input_fixel_folder = argument[0]; + const std::string input_fixel_directory = argument[0]; auto opt = get_options ("value"); if (!opt.size()) throw Exception ("For converting from new to old formats, option -value is compulsory"); @@ -181,9 +181,9 @@ void convert_new2old () opt = get_options ("in_size"); const std::string size_path = opt.size() ? std::string(opt[0][0]) : ""; - Header H_index = FixelFormat::find_index_header (input_fixel_folder); - Header H_dirs = FixelFormat::find_directions_header (input_fixel_folder); - std::vector
H_data = FixelFormat::find_data_headers (input_fixel_folder, H_index, false); + Header H_index = FixelFormat::find_index_header (input_fixel_directory); + Header H_dirs = FixelFormat::find_directions_header (input_fixel_directory); + std::vector
H_data = FixelFormat::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) { @@ -193,7 +193,7 @@ void convert_new2old () size_index = i; } if (value_index == H_data.size()) - throw Exception ("Could not find image in input fixel folder corresponding to -value option"); + throw Exception ("Could not find image in input fixel directory corresponding to -value option"); Header H_out (H_index); H_out.ndim() = 3; diff --git a/cmd/fixelcorrespondence.cpp b/cmd/fixelcorrespondence.cpp index f4b37c84e8..7f8f996325 100644 --- a/cmd/fixelcorrespondence.cpp +++ b/cmd/fixelcorrespondence.cpp @@ -36,10 +36,10 @@ void usage () "The output fixel image will have the same fixels (and directions) of the template."; ARGUMENTS - + Argument ("subject_data", "the input subject fixel data file. This should be a file inside the fixel folder").type_image_in () - + Argument ("template_folder", "the input template fixel folder.").type_image_in () - + Argument ("output_folder", "the output fixel folder.").type_text() - + Argument ("output_data", "the name of the output fixel data file. This will be placed in the output fixel folder").type_image_out (); + + 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_image_in () + + Argument ("output_directory", "the output fixel directory.").type_text() + + Argument ("output_data", "the name of the output fixel data file. This will be placed in the output fixel directory").type_image_out (); OPTIONS + Option ("angle", "the max angle threshold for computing inter-subject fixel correspondence (Default: " + str(DEFAULT_ANGLE_THRESHOLD, 2) + " degrees)") @@ -54,10 +54,10 @@ void run () 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 folder)"); + throw Exception ("please input the specific fixel data file to be converted (not the fixel directory)"); - auto subject_index = FixelFormat::find_index_header (FixelFormat::get_fixel_folder (input_file)).get_image(); - auto subject_directions = FixelFormat::find_directions_header (FixelFormat::get_fixel_folder (input_file)).get_image().with_direct_io(); + auto subject_index = FixelFormat::find_index_header (FixelFormat::get_fixel_directory (input_file)).get_image(); + auto subject_directions = FixelFormat::find_directions_header (FixelFormat::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"); @@ -69,14 +69,14 @@ void run () auto template_directions = FixelFormat::find_directions_header (argument[1]).get_image().with_direct_io(); check_dimensions (subject_index, template_index); - std::string output_fixel_folder = argument[2]; - FixelFormat::copy_index_and_directions_file (argument[1], output_fixel_folder); + std::string output_fixel_directory = argument[2]; + FixelFormat::copy_index_and_directions_file (argument[1], output_fixel_directory); Header output_data_header (template_directions); output_data_header.size(1) = 1; - auto output_data = Image::create (Path::join (output_fixel_folder, 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 fixels to template fixels", template_index, 0, 3)(template_index, subject_index); i; ++i) { + for (auto i = Loop ("mapping subject fixels data to template fixels", template_index, 0, 3)(template_index, subject_index); i; ++i) { template_index.index(3) = 0; uint32_t nfixels_template = template_index.value(); template_index.index(3) = 1; diff --git a/cmd/fixelcrop.cpp b/cmd/fixelcrop.cpp index 18977bc474..d0ed7e6783 100644 --- a/cmd/fixelcrop.cpp +++ b/cmd/fixelcrop.cpp @@ -36,25 +36,25 @@ void usage () "fixel data file the same dimensions as the fixel data file(s) to be cropped."; ARGUMENTS - + Argument ("input_fixel_folder", "the input fixel folder file to be cropped").type_text () + + Argument ("input_fixel_directory", "the input fixel directory file to be cropped").type_text () + Argument ("input_fixel_mask", "the input fixel data file to be cropped").type_image_in () - + Argument ("output_fixel_folder", "the output fixel folder").type_text (); + + Argument ("output_fixel_directory", "the output fixel directory").type_text (); } void run () { - const auto in_folder = argument[0]; - FixelFormat::check_fixel_folder (in_folder); - Header in_index_header = FixelFormat::find_index_header (in_folder); + const auto in_directory = argument[0]; + FixelFormat::check_fixel_directory (in_directory); + Header in_index_header = FixelFormat::find_index_header (in_directory); auto in_index_image = in_index_header.get_image (); auto mask_image = Image::open (argument[1]); FixelFormat::check_fixel_size (in_index_image, mask_image); - const auto out_fixel_folder = argument[2]; - FixelFormat::check_fixel_folder (out_fixel_folder, true); + const auto out_fixel_directory = argument[2]; + FixelFormat::check_fixel_directory (out_fixel_directory, true); Header out_header = Header (in_index_image); size_t total_nfixels = std::stoul (out_header.keyval ()[FixelFormat::n_fixels_key]); @@ -66,11 +66,11 @@ void run () } out_header.keyval ()[FixelFormat::n_fixels_key] = str (total_nfixels); - auto out_index_image = Image::create (Path::join (out_fixel_folder, Path::basename (in_index_image.name())), out_header); + 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 - std::vector
in_headers = FixelFormat::find_data_headers (in_folder, in_index_header, true); + std::vector
in_headers = FixelFormat::find_data_headers (in_directory, in_index_header, true); std::vector > in_data_images; std::vector > out_data_images; for (auto& in_data_header : in_headers) { @@ -79,7 +79,7 @@ void run () 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_folder, Path::basename (in_data_header.name())), + out_data_images.push_back(Image::create (Path::join (out_fixel_directory, Path::basename (in_data_header.name())), out_data_header).with_direct_io()); } diff --git a/cmd/fixelreorient.cpp b/cmd/fixelreorient.cpp index 9eace9beb2..0b7b38526c 100644 --- a/cmd/fixelreorient.cpp +++ b/cmd/fixelreorient.cpp @@ -37,42 +37,42 @@ void usage () "then re-normalising the vector."; ARGUMENTS - + Argument ("fixel_in", "the fixel folder").type_text () + + Argument ("fixel_in", "the fixel directory").type_text () + 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 folder. If the the input and output folders are the same, the existing directions file will " - "be replaced (providing the --force option is supplied). If a new folder is supplied then all " - "fixel data will be copied to the new folder.").type_text (); + + Argument ("fixel_out", "the output fixel directory. If the the input and output directorys are the same, the existing directions file will " + "be replaced (providing the --force option is supplied). If a new directory is supplied then all " + "fixel data will be copied to the new directory.").type_text (); } void run () { - std::string input_fixel_folder = argument[0]; - FixelFormat::check_fixel_folder (input_fixel_folder); + std::string input_fixel_directory = argument[0]; + FixelFormat::check_fixel_directory (input_fixel_directory); - auto input_index_image = FixelFormat::find_index_header (input_fixel_folder).get_image (); + auto input_index_image = FixelFormat::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()); - std::string output_fixel_folder = argument[2]; - FixelFormat::check_fixel_folder (output_fixel_folder, true); + std::string output_fixel_directory = argument[2]; + FixelFormat::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 = FixelFormat::find_directions_header (input_fixel_folder).get_image(); + auto tmp = FixelFormat::find_directions_header (input_fixel_directory).get_image(); input_directions_image = Image::scratch(tmp); threaded_copy (tmp, input_directions_image); output_directions_filename = Path::basename(tmp.name()); } - auto output_directions_image = Image::create (Path::join(output_fixel_folder, 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) { input_index_image.index(3) = 0; @@ -90,9 +90,9 @@ void run () } } - if (output_fixel_folder != input_fixel_folder) { - FixelFormat::copy_index_file (input_fixel_folder, output_fixel_folder); - FixelFormat::copy_all_data_files (input_fixel_folder, output_fixel_folder); + if (output_fixel_directory != input_fixel_directory) { + FixelFormat::copy_index_file (input_fixel_directory, output_fixel_directory); + FixelFormat::copy_all_data_files (input_fixel_directory, output_fixel_directory); } } diff --git a/cmd/fod2fixel.cpp b/cmd/fod2fixel.cpp index d98e920d9c..00ebab5644 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -79,7 +79,7 @@ void usage () ARGUMENTS + Argument ("fod", "the input fod image.").type_image_in () - + Argument ("fixel_folder", "the output fixel folder").type_text(); + + Argument ("fixel_directory", "the output fixel directory").type_text(); OPTIONS @@ -111,7 +111,7 @@ class Segmented_FOD_receiver void commit (); - void set_fixel_folder_output (const std::string& path) { fixel_folder_path = path; } + 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; } @@ -150,7 +150,7 @@ class Segmented_FOD_receiver }; Header H; - std::string fixel_folder_path, index_path, dir_path, afd_path, peak_path, disp_path; + std::string fixel_directory_path, index_path, dir_path, afd_path, peak_path, disp_path; std::vector lobes; uint64_t n_fixels; bool dir_as_peak; @@ -192,7 +192,7 @@ void Segmented_FOD_receiver::commit () using DataImage = Image; using IndexImage = Image; - const auto index_filepath = Path::join (fixel_folder_path, index_path); + const auto index_filepath = Path::join (fixel_directory_path, index_path); std::unique_ptr index_image (nullptr); std::unique_ptr dir_image (nullptr); @@ -218,7 +218,7 @@ void Segmented_FOD_receiver::commit () if (dir_path.size()) { auto dir_header (fixel_data_header); dir_header.size(1) = 3; - dir_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_folder_path, dir_path), dir_header))); + dir_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_directory_path, dir_path), dir_header))); dir_image->index(1) = 0; FixelFormat::check_fixel_size (*index_image, *dir_image); } @@ -226,7 +226,7 @@ void Segmented_FOD_receiver::commit () if (afd_path.size()) { auto afd_header (fixel_data_header); afd_header.size(1) = 1; - afd_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_folder_path, afd_path), afd_header))); + afd_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_directory_path, afd_path), afd_header))); afd_image->index(1) = 0; FixelFormat::check_fixel_size (*index_image, *afd_image); } @@ -234,7 +234,7 @@ void Segmented_FOD_receiver::commit () if (peak_path.size()) { auto peak_header(fixel_data_header); peak_header.size(1) = 1; - peak_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_folder_path, peak_path), peak_header))); + peak_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_directory_path, peak_path), peak_header))); peak_image->index(1) = 0; FixelFormat::check_fixel_size (*index_image, *peak_image); } @@ -242,7 +242,7 @@ void Segmented_FOD_receiver::commit () if (disp_path.size()) { auto disp_header (fixel_data_header); disp_header.size(1) = 1; - disp_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_folder_path, disp_path), disp_header))); + disp_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_directory_path, disp_path), disp_header))); disp_image->index(1) = 0; FixelFormat::check_fixel_size (*index_image, *disp_image); } @@ -309,8 +309,8 @@ void run () Segmented_FOD_receiver receiver (H, dir_as_peak); - auto& fixel_folder_path = argument[1]; - receiver.set_fixel_folder_output (fixel_folder_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()) @@ -337,7 +337,7 @@ void run () if (!receiver.num_outputs ()) throw Exception ("Nothing to do; please specify at least one output image type"); - FixelFormat::check_fixel_folder (fixel_folder_path, true, true); + FixelFormat::check_fixel_directory (fixel_directory_path, true, true); FMLS::FODQueueWriter writer (fod_data, mask); diff --git a/cmd/voxel2fixel.cpp b/cmd/voxel2fixel.cpp index f8be23d500..bac3f403f1 100644 --- a/cmd/voxel2fixel.cpp +++ b/cmd/voxel2fixel.cpp @@ -37,8 +37,8 @@ void usage () ARGUMENTS + Argument ("image_in", "the input image.").type_image_in() - + Argument ("fixel_folder_in", "the input fixel folder. Used to define the fixels and their directions").type_text() - + Argument ("fixel_folder_out", "the output fixel folder. This can be the same as the input folder if desired").type_text() + + Argument ("fixel_directory_in", "the input fixel directory. Used to define the fixels and their directions").type_text() + + Argument ("fixel_directory_out", "the output fixel directory. 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_image_out(); } @@ -46,20 +46,20 @@ void usage () void run () { auto scalar = Image::open (argument[0]); - std::string input_fixel_folder = argument[1]; - FixelFormat::check_fixel_folder (input_fixel_folder); - auto input_fixel_index = FixelFormat::find_index_header (input_fixel_folder).get_image(); + std::string input_fixel_directory = argument[1]; + FixelFormat::check_fixel_directory (input_fixel_directory); + auto input_fixel_index = FixelFormat::find_index_header (input_fixel_directory).get_image(); check_dimensions (scalar, input_fixel_index, 0, 3); - std::string output_fixel_folder = argument[2]; - if (input_fixel_folder != output_fixel_folder) { - ProgressBar progress ("copying fixel index and directions file into output folder"); + 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"); progress++; - FixelFormat::copy_index_and_directions_file (input_fixel_folder, output_fixel_folder); + FixelFormat::copy_index_and_directions_file (input_fixel_directory, output_fixel_directory); progress++; } - auto output_fixel_data = Image::create (Path::join(output_fixel_folder, argument[3]), FixelFormat::data_header_from_index (input_fixel_index)); + auto output_fixel_data = Image::create (Path::join(output_fixel_directory, argument[3]), FixelFormat::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 = FixelFormat::FixelLoop (input_fixel_index) (output_fixel_data); f; ++f) diff --git a/cmd/warp2metric.cpp b/cmd/warp2metric.cpp index b1bbf5e4e3..ee554d09dd 100644 --- a/cmd/warp2metric.cpp +++ b/cmd/warp2metric.cpp @@ -46,9 +46,9 @@ void usage () 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_folder output_fixel_folder fc.mif") - + Argument ("template_fixel_folder").type_image_in() - + Argument ("output_fixel_folder").type_text() + "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_image_out() + Option ("jmat", "output a Jacobian matrix image stored in column-major order along the 4th dimension." @@ -78,14 +78,14 @@ void run () auto opt = get_options ("fc"); if (opt.size()) { - std::string template_fixel_folder (opt[0][0]); - fixel_template_index = FixelFormat::find_index_header (template_fixel_folder).get_image(); - fixel_template_directions = FixelFormat::find_directions_header (template_fixel_folder).get_image().with_direct_io(); - - std::string output_fixel_folder (opt[0][1]); - if (template_fixel_folder != output_fixel_folder) { - FixelFormat::copy_index_file (template_fixel_folder, output_fixel_folder); - FixelFormat::copy_directions_file (template_fixel_folder, output_fixel_folder); + std::string template_fixel_directory (opt[0][0]); + fixel_template_index = FixelFormat::find_index_header (template_fixel_directory).get_image(); + fixel_template_directions = FixelFormat::find_directions_header (template_fixel_directory).get_image().with_direct_io(); + + std::string output_fixel_directory (opt[0][1]); + if (template_fixel_directory != output_fixel_directory) { + FixelFormat::copy_index_file (template_fixel_directory, output_fixel_directory); + FixelFormat::copy_directions_file (template_fixel_directory, output_fixel_directory); } uint32_t num_fixels = 0; @@ -93,7 +93,7 @@ void run () for (auto l = Loop (fixel_template_index, 0, 3) (fixel_template_index); l; ++l) num_fixels += fixel_template_index.value(); - fc_output_data = Image::create (Path::join (output_fixel_folder, opt[0][2]), FixelFormat::data_header_from_index (fixel_template_index)); + fc_output_data = Image::create (Path::join (output_fixel_directory, opt[0][2]), FixelFormat::data_header_from_index (fixel_template_index)); } diff --git a/docs/reference/commands/fixel2tsf.rst b/docs/reference/commands/fixel2tsf.rst index 82893e2c12..bcb6f3610f 100644 --- a/docs/reference/commands/fixel2tsf.rst +++ b/docs/reference/commands/fixel2tsf.rst @@ -10,7 +10,7 @@ Synopsis fixel2tsf [ options ] fixel_in tracks tsf -- *fixel_in*: the input fixel data file (within the fixel folder) +- *fixel_in*: the input fixel data file (within the fixel directory) - *tracks*: the input track file - *tsf*: the output track scalar file diff --git a/docs/reference/commands/fixelcfestats.rst b/docs/reference/commands/fixelcfestats.rst index 9e43b9cc61..aa0bb972fd 100644 --- a/docs/reference/commands/fixelcfestats.rst +++ b/docs/reference/commands/fixelcfestats.rst @@ -8,14 +8,14 @@ Synopsis :: - fixelcfestats [ options ] in_fixel_folder subjects design contrast tracks out_fixel_folder + fixelcfestats [ options ] in_fixel_directory subjects design contrast tracks out_fixel_directory -- *in_fixel_folder*: the fixel folder containing the data files for each subject (after obtaining fixel correspondence -- *subjects*: a text file listing the subject identifiers (one per line). This should correspond with the filenames in the fixel folder (including the file extension), and be listed in the same order as the rows of the design matrix. +- *in_fixel_directory*: the fixel directory containing the data files for each subject (after obtaining fixel correspondence +- *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. - *design*: the design matrix. Note that a column of 1's will need to be added for correlations. - *contrast*: the contrast vector, specified as a single row of weights - *tracks*: the tracks used to determine fixel-fixel connectivity -- *out_fixel_folder*: the output folder where results will be saved. Will be created if it does not exist +- *out_fixel_directory*: the output directory where results will be saved. Will be created if it does not exist Description ----------- diff --git a/docs/reference/commands/fixelconvert.rst b/docs/reference/commands/fixelconvert.rst index fe8a8f9abb..d3041d7327 100644 --- a/docs/reference/commands/fixelconvert.rst +++ b/docs/reference/commands/fixelconvert.rst @@ -10,13 +10,13 @@ Synopsis fixelconvert [ options ] fixel_in fixel_out -- *fixel_in*: the input fixel file / folder. -- *fixel_out*: the output fixel file / folder. +- *fixel_in*: the input fixel file / directory. +- *fixel_out*: the output fixel file / directory. Description ----------- -convert between the old format fixel image (*.msf / *.msh) and the new fixel folder format +convert between the old format fixel image (*.msf / *.msh) and the new fixel directory format Options ------- diff --git a/docs/reference/commands/fixelcorrespondence.rst b/docs/reference/commands/fixelcorrespondence.rst index fb44baf47e..faf861e749 100644 --- a/docs/reference/commands/fixelcorrespondence.rst +++ b/docs/reference/commands/fixelcorrespondence.rst @@ -8,12 +8,12 @@ Synopsis :: - fixelcorrespondence [ options ] subject_data template_folder output_folder output_data + fixelcorrespondence [ options ] subject_data template_directory output_directory output_data -- *subject_data*: the input subject fixel data file. This should be a file inside the fixel folder -- *template_folder*: the input template fixel folder. -- *output_folder*: the output fixel folder. -- *output_data*: the name of the output fixel data file. This will be placed in the output fixel folder +- *subject_data*: the input subject fixel data file. This should be a file inside the fixel directory +- *template_directory*: the input template fixel directory. +- *output_directory*: the output fixel directory. +- *output_data*: the name of the output fixel data file. This will be placed in the output fixel directory Description ----------- diff --git a/docs/reference/commands/fixelcrop.rst b/docs/reference/commands/fixelcrop.rst index 365956b0cb..eebcaf8beb 100644 --- a/docs/reference/commands/fixelcrop.rst +++ b/docs/reference/commands/fixelcrop.rst @@ -8,11 +8,11 @@ Synopsis :: - fixelcrop [ options ] input_fixel_folder input_fixel_mask output_fixel_folder + fixelcrop [ options ] input_fixel_directory input_fixel_mask output_fixel_directory -- *input_fixel_folder*: the input fixel folder file to be cropped +- *input_fixel_directory*: the input fixel directory file to be cropped - *input_fixel_mask*: the input fixel data file to be cropped -- *output_fixel_folder*: the output fixel folder +- *output_fixel_directory*: the output fixel directory Description ----------- diff --git a/docs/reference/commands/fixelreorient.rst b/docs/reference/commands/fixelreorient.rst index d6aadcc580..792604f622 100644 --- a/docs/reference/commands/fixelreorient.rst +++ b/docs/reference/commands/fixelreorient.rst @@ -10,9 +10,9 @@ Synopsis fixelreorient [ options ] fixel_in warp fixel_out -- *fixel_in*: the fixel folder +- *fixel_in*: the fixel directory - *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 -- *fixel_out*: the output fixel folder. If the the input and output folders are the same, the existing directions file will be replaced (providing the --force option is supplied). If a new folder is supplied then all fixel data will be copied to the new folder. +- *fixel_out*: the output fixel directory. If the the input and output directorys are the same, the existing directions file will be replaced (providing the --force option is supplied). If a new directory is supplied then all fixel data will be copied to the new directory. Description ----------- diff --git a/docs/reference/commands/fod2fixel.rst b/docs/reference/commands/fod2fixel.rst index f3f9eecba0..f0789b4277 100644 --- a/docs/reference/commands/fod2fixel.rst +++ b/docs/reference/commands/fod2fixel.rst @@ -8,10 +8,10 @@ Synopsis :: - fod2fixel [ options ] fod fixel_folder + fod2fixel [ options ] fod fixel_directory - *fod*: the input fod image. -- *fixel_folder*: the output fixel folder +- *fixel_directory*: the output fixel directory Description ----------- diff --git a/docs/reference/commands/voxel2fixel.rst b/docs/reference/commands/voxel2fixel.rst index 3664898336..ff7e4233d8 100644 --- a/docs/reference/commands/voxel2fixel.rst +++ b/docs/reference/commands/voxel2fixel.rst @@ -8,11 +8,11 @@ Synopsis :: - voxel2fixel [ options ] image_in fixel_folder_in fixel_folder_out fixel_data_out + voxel2fixel [ options ] image_in fixel_directory_in fixel_directory_out fixel_data_out - *image_in*: the input image. -- *fixel_folder_in*: the input fixel folder. Used to define the fixels and their directions -- *fixel_folder_out*: the output fixel folder. This can be the same as the input folder if desired +- *fixel_directory_in*: the input fixel directory. Used to define the fixels and their directions +- *fixel_directory_out*: the output fixel directory. This can be the same as the input directory if desired - *fixel_data_out*: the name of the fixel data image. Description diff --git a/docs/reference/commands/warp2metric.rst b/docs/reference/commands/warp2metric.rst index 8880e8ac9c..6db95d5023 100644 --- a/docs/reference/commands/warp2metric.rst +++ b/docs/reference/commands/warp2metric.rst @@ -20,7 +20,7 @@ compute fixel or voxel-wise metrics from a 4D deformation field Options ------- -- **-fc template_fixel_folder output_fixel_folder output_fixel_data** 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_folder output_fixel_folder fc.mif +- **-fc template_fixel_directory output_fixel_directory output_fixel_data** 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 - **-jmat output** 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 diff --git a/lib/fixel_format/helpers.h b/lib/fixel_format/helpers.h index 961924ec12..f3b9c39a26 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/fixel_format/helpers.h @@ -19,6 +19,7 @@ #include "formats/mrtrix_utils.h" #include "fixel_format/keys.h" #include "algo/loop.h" +#include "image_diff.h" namespace MR { @@ -83,12 +84,12 @@ namespace MR throw InvalidImageException (in.name() + " is not a valid fixel data file. Expected a 3-dimensional image of size n x m x 1"); } - FORCE_INLINE std::string get_fixel_folder (const std::string& fixel_file) { - std::string fixel_folder = Path::dirname (fixel_file); + FORCE_INLINE std::string get_fixel_directory (const std::string& fixel_file) { + std::string fixel_directory = Path::dirname (fixel_file); // assume the user is running the command from within the fixel directory - if (fixel_folder.empty()) - fixel_folder = Path::cwd(); - return fixel_folder; + if (fixel_directory.empty()) + fixel_directory = Path::cwd(); + return fixel_directory; } @@ -154,10 +155,10 @@ namespace MR } - FORCE_INLINE void check_fixel_folder (const std::string &path, bool create_if_missing = false, bool check_if_empty = false) + FORCE_INLINE void check_fixel_directory (const std::string &path, bool create_if_missing = false, bool check_if_empty = false) { std::string path_temp = path; - // handle the use case when a fixel command is run from inside a fixel folder + // handle the use case when a fixel command is run from inside a fixel directory if (path.empty()) path_temp = Path::cwd(); @@ -175,32 +176,32 @@ namespace MR } - FORCE_INLINE Header find_index_header (const std::string &fixel_folder_path) + FORCE_INLINE Header find_index_header (const std::string &fixel_directory_path) { Header header; - check_fixel_folder (fixel_folder_path); + check_fixel_directory (fixel_directory_path); for (std::initializer_list::iterator it = FixelFormat::supported_fixel_formats.begin(); it != FixelFormat::supported_fixel_formats.end(); ++it) { - std::string full_path = Path::join (fixel_folder_path, "index" + *it); + std::string full_path = Path::join (fixel_directory_path, "index" + *it); if (Path::exists(full_path)) { if (header.valid()) - throw InvalidFixelDirectoryException ("Multiple index images found in directory " + fixel_folder_path); + throw InvalidFixelDirectoryException ("Multiple index images found in directory " + fixel_directory_path); header = Header::open (full_path); } } if (!header.valid()) - throw InvalidFixelDirectoryException ("Could not find index image in directory " + fixel_folder_path); + throw InvalidFixelDirectoryException ("Could not find index image in directory " + fixel_directory_path); check_index_image (header); return header; } - FORCE_INLINE std::vector
find_data_headers (const std::string &fixel_folder_path, const Header &index_header, const bool include_directions = false) + FORCE_INLINE std::vector
find_data_headers (const std::string &fixel_directory_path, const Header &index_header, const bool include_directions = false) { check_index_image (index_header); - auto dir_walker = Path::Dir (fixel_folder_path); + auto dir_walker = Path::Dir (fixel_directory_path); std::vector file_names; { std::string temp; @@ -213,7 +214,7 @@ namespace MR for (auto fname : file_names) { if (Path::has_suffix (fname, FixelFormat::supported_fixel_formats)) { try { - auto H = Header::open (Path::join (fixel_folder_path, fname)); + auto H = Header::open (Path::join (fixel_directory_path, fname)); if (is_data_file (H)) { if (fixels_match (index_header, H)) { if (!is_directions_file (H) || include_directions) @@ -233,24 +234,24 @@ namespace MR - FORCE_INLINE Header find_directions_header (const std::string fixel_folder_path) + FORCE_INLINE Header find_directions_header (const std::string fixel_directory_path) { bool directions_found (false); Header header; - check_fixel_folder (fixel_folder_path); - Header index_header = FixelFormat::find_index_header (fixel_folder_path); + check_fixel_directory (fixel_directory_path); + Header index_header = FixelFormat::find_index_header (fixel_directory_path); - auto dir_walker = Path::Dir (fixel_folder_path); + auto dir_walker = Path::Dir (fixel_directory_path); std::string fname; while ((fname = dir_walker.read_name ()).size ()) { Header tmp_header; - auto full_path = Path::join (fixel_folder_path, fname); + auto full_path = Path::join (fixel_directory_path, fname); if (Path::has_suffix (fname, FixelFormat::supported_fixel_formats) && is_directions_file (tmp_header = Header::open (full_path))) { if (is_directions_file (tmp_header)) { if (fixels_match (index_header, tmp_header)) { if (directions_found == true) - throw Exception ("multiple directions files found in fixel image folder: " + fixel_folder_path); + throw Exception ("multiple directions files found in fixel image directory: " + fixel_directory_path); directions_found = true; header = std::move(tmp_header); } else { @@ -261,7 +262,7 @@ namespace MR } if (!directions_found) - throw InvalidFixelDirectoryException ("Could not find directions image in directory " + fixel_folder_path); + throw InvalidFixelDirectoryException ("Could not find directions image in directory " + fixel_directory_path); return header; } @@ -287,54 +288,82 @@ namespace MR return header; } - //! Copy a file from one fixel folder into another. - FORCE_INLINE void copy_fixel_file (const std::string& input_file_path, const std::string& output_folder) { - check_fixel_folder (output_folder, true); - std::string output_path = Path::join (output_folder, Path::basename (input_file_path)); + //! Copy a file from one fixel directory into another. + FORCE_INLINE void copy_fixel_file (const std::string& input_file_path, const std::string& output_directory) { + check_fixel_directory (output_directory, true); + std::string output_path = Path::join (output_directory, Path::basename (input_file_path)); Header input_header = Header::open (input_file_path); auto input_image = input_header.get_image(); auto output_image = Image::create (output_path, input_header); threaded_copy (input_image, output_image); } - //! Copy the index file from one fixel folder into another - FORCE_INLINE void copy_index_file (const std::string& input_folder, const std::string& output_folder) { - Header input_header = FixelFormat::find_index_header (input_folder); - check_fixel_folder (output_folder, true); - auto output_image = Image::create (Path::join (output_folder, Path::basename (input_header.name())), input_header); - auto input_image = input_header.get_image(); - threaded_copy (input_image, output_image); + //! Copy the index file from one fixel directory into another + FORCE_INLINE void copy_index_file (const std::string& input_directory, const std::string& output_directory) { + Header input_header = FixelFormat::find_index_header (input_directory); + check_fixel_directory (output_directory, true); + + std::string output_path = Path::join (output_directory, Path::basename (input_header.name())); + + // If the index file already exists check it is the same as the input index file + if (Path::exists (output_path) && !App::overwrite_files) { + auto input_image = input_header.get_image(); + auto output_image = Image::open (output_path); + + if (!images_match_abs (input_image, output_image)) + throw Exception ("output sparse image directory (" + output_directory + ") already contains index file, " + "which is not the same as the expected output. Use -force to override if desired"); + + } else { + auto output_image = Image::create (Path::join (output_directory, Path::basename (input_header.name())), input_header); + auto input_image = input_header.get_image(); + threaded_copy (input_image, output_image); + } } - //! Copy the directions file from one fixel folder into another. - FORCE_INLINE void copy_directions_file (const std::string& input_folder, const std::string& output_folder) { - Header directions_header = FixelFormat::find_directions_header (input_folder); - copy_fixel_file (directions_header.name(), output_folder); + //! Copy the directions file from one fixel directory into another. + FORCE_INLINE void copy_directions_file (const std::string& input_directory, const std::string& output_directory) { + Header input_header = FixelFormat::find_directions_header (input_directory); + std::string output_path = Path::join (output_directory, Path::basename (input_header.name())); + + // If the index file already exists check it is the same as the input index file + if (Path::exists (output_path) && !App::overwrite_files) { + auto input_image = input_header.get_image(); + auto output_image = Image::open (output_path); + + if (!images_match_abs (input_image, output_image)) + throw Exception ("output sparse image directory (" + output_directory + ") already contains a directions file, " + "which is not the same as the expected output. Use -force to override if desired"); + + } else { + copy_fixel_file (input_header.name(), output_directory); + } + } - FORCE_INLINE void copy_index_and_directions_file (const std::string& input_folder, const std::string &output_folder) { - copy_index_file (input_folder, output_folder); - copy_directions_file (input_folder, output_folder); + FORCE_INLINE void copy_index_and_directions_file (const std::string& input_directory, const std::string &output_directory) { + copy_index_file (input_directory, output_directory); + copy_directions_file (input_directory, output_directory); } - //! Copy all data files in a fixel folder into another folder. Data files do not include the index or directions file. - FORCE_INLINE void copy_all_data_files (const std::string &input_folder, const std::string &output_folder) { - for (auto& input_header : FixelFormat::find_data_headers (input_folder, FixelFormat::find_index_header (input_folder))) - copy_fixel_file (input_header.name(), output_folder); + //! Copy all data files in a fixel directory into another directory. Data files do not include the index or directions file. + FORCE_INLINE void copy_all_data_files (const std::string &input_directory, const std::string &output_directory) { + for (auto& input_header : FixelFormat::find_data_headers (input_directory, FixelFormat::find_index_header (input_directory))) + copy_fixel_file (input_header.name(), output_directory); } - //! open a data file. checks that a user has not input a fixel folder or index image + //! open a data file. checks that a user has not input a fixel directory or index image template Image open_fixel_data_file (const std::string& input_file) { if (Path::is_dir (input_file)) - throw Exception ("please input the specific fixel data file to be converted (not the fixel folder)"); + throw Exception ("please input the specific fixel data file to be converted (not the fixel directory)"); Header in_data_header = Header::open (input_file); FixelFormat::check_data_file (in_data_header); auto in_data_image = in_data_header.get_image(); - Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_folder (input_file)); + Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_directory (input_file)); if (input_file == in_index_header.name()) throw Exception ("input fixel data file cannot be the index file"); diff --git a/lib/image_diff.h b/lib/image_diff.h new file mode 100644 index 0000000000..17f78ca7a5 --- /dev/null +++ b/lib/image_diff.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __image_check__h__ +#define __image_check__h__ + +#include "image_helpers.h" +#include "algo/loop.h" + +namespace MR +{ + + //! check image headers are the same (dimensions, spacing & transform) + template + inline void check_headers (HeaderType1& in1, HeaderType2& in2) + { + check_dimensions (in1, in2); + for (size_t i = 0; i < in1.ndim(); ++i) { + if (std::isfinite (in1.spacing(i))) + if (in1.spacing(i) != in2.spacing(i)) + throw Exception ("images \"" + in1.name() + "\" and \"" + in2.name() + "\" do not have matching voxel spacings " + + str(in1.spacing(i)) + " vs " + str(in2.spacing(i))); + } + for (size_t i = 0; i < 3; ++i) { + for (size_t j = 0; j < 4; ++j) { + if (std::abs (in1.transform().matrix()(i,j) - in2.transform().matrix()(i,j)) > 0.001) + throw Exception ("images \"" + in1.name() + "\" and \"" + in2.name() + "\" do not have matching header transforms " + + "\n" + str(in1.transform().matrix()) + "vs \n " + str(in2.transform().matrix()) + ")"); + } + } + } + + + //! check images are the same within a absolute tolerance + template + inline void check_images_abs (ImageType1& in1, ImageType2& in2, const double tol = 0.0) + { + check_headers (in1, in2); + ThreadedLoop (in1) + .run ([&tol] (const decltype(in1)& a, const decltype(in2)& b) { + if (std::abs (cdouble (a.value()) - cdouble (b.value())) > tol) + throw Exception ("images \"" + a.name() + "\" and \"" + b.name() + "\" do not match within absolute precision of " + str(tol) + + " (" + str(cdouble (a.value())) + " vs " + str(cdouble (b.value())) + ")"); + }, in1, in2); + } + + + //! check images are the same within a fractional tolerance + template + inline void check_images_frac (ImageType1& in1, ImageType2& in2, const double tol = 0.0) { + + check_headers (in1, in2); + ThreadedLoop (in1) + .run ([&tol] (const decltype(in1)& a, const decltype(in2)& b) { + if (std::abs ((cdouble (a.value()) - cdouble (b.value())) / (0.5 * (cdouble (a.value()) + cdouble (b.value())))) > tol) + throw Exception ("images \"" + a.name() + "\" and \"" + b.name() + "\" do not match within fractional precision of " + str(tol) + + " (" + str(cdouble (a.value())) + " vs " + str(cdouble (b.value())) + ")"); + }, in1, in2); + } + + //! check images are the same within a fractional tolerance relative to the maximum value in the voxel + template + inline void check_images_voxel (ImageType1& in1, ImageType2& in2, const double tol = 0.0) + { + auto func = [&tol] (decltype(in1)& a, decltype(in2)& b) { + double maxa = 0.0, maxb = 0.0; + for (auto l = Loop(3) (a, b); l; ++l) { + maxa = std::max (maxa, std::abs (cdouble(a.value()))); + maxb = std::max (maxb, std::abs (cdouble(b.value()))); + } + const double threshold = tol * 0.5 * (maxa + maxb); + for (auto l = Loop(3) (a, b); l; ++l) { + if (std::abs (cdouble (a.value()) - cdouble (b.value())) > threshold) + throw Exception ("images \"" + a.name() + "\" and \"" + b.name() + "\" do not match within " + str(tol) + " of maximal voxel value" + + " (" + str(cdouble (a.value())) + " vs " + str(cdouble (b.value())) + ")"); + } + }; + + ThreadedLoop (in1, 0, 3).run (func, in1, in2); + } + + //! check image headers are the same (dimensions, spacing & transform) + template + inline bool headers_match (HeaderType1& in1, HeaderType2& in2) + { + if (!dimensions_match (in1, in2)) + return false; + if (!spacings_match (in1, in2)) + return false; + if (!transforms_match (in1, in2)) + return false; + return true; + } + + + //! check images are the same within a absolute tolerance + template + inline bool images_match_abs (ImageType1& in1, ImageType2& in2, const double tol = 0.0) + { + headers_match (in1, in2); + for (auto i = Loop (in1)(in1, in2); i; ++i) + if (std::abs (cdouble (in1.value()) - cdouble (in2.value())) > tol) + return false; + return true; + } + + + + + + +} + +#endif + + + + + diff --git a/lib/image_helpers.h b/lib/image_helpers.h index 8780cfc721..d6b8876acc 100644 --- a/lib/image_helpers.h +++ b/lib/image_helpers.h @@ -398,7 +398,28 @@ namespace MR throw Exception ("dimension mismatch between \"" + in1.name() + "\" and \"" + in2.name() + "\""); } + template + inline void check_transform (const HeaderType1& in1, const HeaderType2& in2, const double tol = 0.0) { + for (size_t i = 0; i < 3; ++i) { + for (size_t j = 0; j < 4; ++j) { + if (std::abs (in1.transform().matrix()(i,j) - in2.transform().matrix()(i,j)) > tol) + throw Exception ("images \"" + in1.name() + "\" and \"" + in2.name() + "\" do not have matching header transforms " + + "\n" + str(in1.transform().matrix()) + "vs \n " + str(in2.transform().matrix()) + ")"); + } + } + } + + template + inline bool transforms_match (const HeaderType1 in1, const HeaderType2 in2, const double tol = 0.0) { + for (size_t i = 0; i < 3; ++i) { + for (size_t j = 0; j < 4; ++j) { + if (std::abs (in1.transform().matrix()(i,j) - in2.transform().matrix()(i,j)) > tol) + return false; + } + } + return true; + } template inline void squeeze_dim (HeaderType& in, size_t from_axis = 3) diff --git a/testing/data b/testing/data index d5b956641b..d79561a3da 160000 --- a/testing/data +++ b/testing/data @@ -1 +1 @@ -Subproject commit d5b956641b0f7aa1ec075dd78ae7983daae4dda8 +Subproject commit d79561a3da099d8f9589df22a7266752ecc02432 diff --git a/testing/src/diff_images.h b/testing/src/diff_images.h new file mode 100644 index 0000000000..400577aaed --- /dev/null +++ b/testing/src/diff_images.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + +#ifndef __testing_diff_images_h__ +#define __testing_diff_images_h__ + +#include "progressbar.h" +#include "datatype.h" + +#include "image.h" +#include "image_helpers.h" +#include "image_diff.h" + +namespace MR +{ + namespace Testing + { + + + const App::OptionGroup Diff_Image_Options = + App::OptionGroup ("Testing image options") + + App::Option ("abs", "specify an absolute tolerance") + + App::Argument ("tolerance").type_float (0.0) + + App::Option ("frac", "specify a fractional tolerance") + + App::Argument ("tolerance").type_float (0.0) + + App::Option ("voxel", "specify a fractional tolerance relative to the maximum value in the voxel") + + App::Argument ("tolerance").type_float (0.0); + + + + template + void diff_images (ImageType1& in1, ImageType2& in2) + { + + double tolerance = 0.0; + auto abs_opt = App::get_options ("abs"); + auto frac_opt = App::get_options ("frac"); + auto voxel_opt = App::get_options ("voxel"); + + if (frac_opt.size()) { + tolerance = frac_opt[0][0]; + check_images_frac (in1, in2, tolerance); + } else if (voxel_opt.size()) { + tolerance = voxel_opt[0][0]; + check_images_voxel (in1, in2, tolerance); + } else if (abs_opt.size()){ + tolerance = abs_opt[0][0]; + check_images_abs (in1, in2, tolerance); + } else { + check_images_abs (in1, in2, tolerance); + } + + } + } +} + +#endif diff --git a/testing/tests/voxel2fixel b/testing/tests/voxel2fixel index 85c974afd4..f5610b7fc4 100644 --- a/testing/tests/voxel2fixel +++ b/testing/tests/voxel2fixel @@ -1 +1 @@ - voxel2fixel dwi_mean.mif afd.msf tmp.msf -force && testing_diff_fixel tmp.msf voxel2fixel/out.msf 1e-5 +voxel2fixel dwi_mean.mif fixel_image voxel2fixel_tmp dwi_mean.mif -force && testing_diff_fixel voxel2fixel voxel2fixel_tmp -frac 1e-4 From 5e98464b15ddc6c864cce9605decc0ff0b528a25 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Tue, 15 Nov 2016 13:45:21 +1100 Subject: [PATCH 221/723] Added missing changes from previous commit --- testing/cmd/testing_diff_fixel.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/testing/cmd/testing_diff_fixel.cpp b/testing/cmd/testing_diff_fixel.cpp index 2887797ac0..c7d15ea8be 100644 --- a/testing/cmd/testing_diff_fixel.cpp +++ b/testing/cmd/testing_diff_fixel.cpp @@ -33,8 +33,8 @@ void usage () + "compare two fixel images for differences, within specified tolerance."; ARGUMENTS - + Argument ("fixel1", "fixel folder.").type_text () - + Argument ("fixel2", "another fixel folder.").type_text (); + + Argument ("fixel1", "fixel directory.").type_text () + + Argument ("fixel2", "another fixel directory.").type_text (); OPTIONS + Testing::Diff_Image_Options; @@ -44,29 +44,29 @@ void usage () void run () { - std::string fixel_folder1 = argument[0]; - FixelFormat::check_fixel_folder (fixel_folder1); - std::string fixel_folder2 = argument[1]; - FixelFormat::check_fixel_folder (fixel_folder2); + std::string fixel_directory1 = argument[0]; + FixelFormat::check_fixel_directory (fixel_directory1); + std::string fixel_directory2 = argument[1]; + FixelFormat::check_fixel_directory (fixel_directory2); - if (fixel_folder1 == fixel_folder2) - throw Exception ("Input fixel folders are the same"); + if (fixel_directory1 == fixel_directory2) + throw Exception ("Input fixel directorys are the same"); - auto dir_walker1 = Path::Dir (fixel_folder1); + auto dir_walker1 = Path::Dir (fixel_directory1); std::string fname; while ((fname = dir_walker1.read_name()).size()) { - auto in1 = Image::open (Path::join (fixel_folder1, fname)); - std::string filename2 = Path::join (fixel_folder2, fname); + auto in1 = Image::open (Path::join (fixel_directory1, fname)); + std::string filename2 = Path::join (fixel_directory2, fname); if (!Path::exists (filename2)) - throw Exception ("File (" + fname + ") exists in fixel folder (" + fixel_folder1 + ") but not in fixel folder (" + fixel_folder2 + ") "); + throw Exception ("File (" + fname + ") exists in fixel directory (" + fixel_directory1 + ") but not in fixel directory (" + fixel_directory2 + ") "); auto in2 = Image::open (filename2); Testing::diff_images (in1, in2); } - auto dir_walker2 = Path::Dir (fixel_folder2); + auto dir_walker2 = Path::Dir (fixel_directory2); while ((fname = dir_walker2.read_name()).size()) { - std::string filename1 = Path::join (fixel_folder1, fname); + std::string filename1 = Path::join (fixel_directory1, fname); if (!Path::exists (filename1)) - throw Exception ("File (" + fname + ") exists in fixel folder (" + fixel_folder2 + ") but not in fixel folder (" + fixel_folder1 + ") "); + throw Exception ("File (" + fname + ") exists in fixel directory (" + fixel_directory2 + ") but not in fixel directory (" + fixel_directory1 + ") "); } CONSOLE ("data checked OK"); } From 850aa5310928ca35bcabe6cbdd377a769faa7af4 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Tue, 15 Nov 2016 14:20:16 +1100 Subject: [PATCH 222/723] added tests for voxel2fixel --- testing/data | 2 +- testing/tests/voxel2fixel | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/data b/testing/data index d79561a3da..af65cd82c7 160000 --- a/testing/data +++ b/testing/data @@ -1 +1 @@ -Subproject commit d79561a3da099d8f9589df22a7266752ecc02432 +Subproject commit af65cd82c74ed8b443cbd44061fe27d77f6580c9 diff --git a/testing/tests/voxel2fixel b/testing/tests/voxel2fixel index f5610b7fc4..8450ff72f7 100644 --- a/testing/tests/voxel2fixel +++ b/testing/tests/voxel2fixel @@ -1 +1 @@ -voxel2fixel dwi_mean.mif fixel_image voxel2fixel_tmp dwi_mean.mif -force && testing_diff_fixel voxel2fixel voxel2fixel_tmp -frac 1e-4 +voxel2fixel dwi_mean.mif fixel_image voxel2fixel_tmp dwi_mean.mif -force && testing_diff_fixel voxel2fixel voxel2fixel_tmp -frac 1e-4; rm -rf voxel2fixel_tmp From d65da3cb027cee45c62c9b19e3cb4b992794a6df Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Tue, 15 Nov 2016 14:33:02 +1100 Subject: [PATCH 223/723] Added test for fixelcrop --- cmd/fixelcrop.cpp | 8 +++++--- testing/data | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cmd/fixelcrop.cpp b/cmd/fixelcrop.cpp index d0ed7e6783..47658d917a 100644 --- a/cmd/fixelcrop.cpp +++ b/cmd/fixelcrop.cpp @@ -36,9 +36,11 @@ void usage () "fixel data file the same dimensions as the fixel data file(s) to be cropped."; ARGUMENTS - + Argument ("input_fixel_directory", "the input fixel directory file to be cropped").type_text () - + Argument ("input_fixel_mask", "the input fixel data file to be cropped").type_image_in () - + Argument ("output_fixel_directory", "the output fixel directory").type_text (); + + Argument ("input_fixel_directory", "input fixel directory, all data files and directions " + "file will be cropped and saved in the output fixel directory").type_text () + + 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_text (); } diff --git a/testing/data b/testing/data index af65cd82c7..92b6f44eab 160000 --- a/testing/data +++ b/testing/data @@ -1 +1 @@ -Subproject commit af65cd82c74ed8b443cbd44061fe27d77f6580c9 +Subproject commit 92b6f44eabd2372a19c62cb0a398d3f2640bc67a From e3dda9c61c5072632f1446e1989a2d1d9408e482 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Tue, 15 Nov 2016 15:41:40 +1100 Subject: [PATCH 224/723] Added tests for fixelconvert --- cmd/fixelconvert.cpp | 8 +-- docs/reference/commands/fixelcrop.rst | 6 +- testing/cmd/testing_diff_fixel_old.cpp | 93 ++++++++++++++++++++++++++ testing/data | 2 +- 4 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 testing/cmd/testing_diff_fixel_old.cpp diff --git a/cmd/fixelconvert.cpp b/cmd/fixelconvert.cpp index 4e23179f16..8094148cc7 100644 --- a/cmd/fixelconvert.cpp +++ b/cmd/fixelconvert.cpp @@ -134,7 +134,7 @@ void convert_old2new () 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(); } @@ -156,7 +156,7 @@ void convert_old2new () 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"); + throw Exception ("mismatch in fixel directions between input and template images"); } value_image.index(0) = offset; value_image.value() = input.value()[f].value; @@ -176,7 +176,7 @@ void convert_new2old () const std::string input_fixel_directory = argument[0]; auto opt = get_options ("value"); if (!opt.size()) - throw Exception ("For converting from new to old formats, option -value is compulsory"); + 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]) : ""; @@ -193,7 +193,7 @@ void convert_new2old () 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); H_out.ndim() = 3; diff --git a/docs/reference/commands/fixelcrop.rst b/docs/reference/commands/fixelcrop.rst index eebcaf8beb..5b0424d7ee 100644 --- a/docs/reference/commands/fixelcrop.rst +++ b/docs/reference/commands/fixelcrop.rst @@ -10,9 +10,9 @@ Synopsis fixelcrop [ options ] input_fixel_directory input_fixel_mask output_fixel_directory -- *input_fixel_directory*: the input fixel directory file to be cropped -- *input_fixel_mask*: the input fixel data file to be cropped -- *output_fixel_directory*: the output fixel directory +- *input_fixel_directory*: input fixel directory, all data files and directions file will be cropped and saved in the output fixel directory +- *input_fixel_mask*: the input fixel data file defining which fixels to crop. Fixels with zero values will be removed +- *output_fixel_directory*: the output directory to store the cropped directions and data files Description ----------- diff --git a/testing/cmd/testing_diff_fixel_old.cpp b/testing/cmd/testing_diff_fixel_old.cpp new file mode 100644 index 0000000000..4620e87660 --- /dev/null +++ b/testing/cmd/testing_diff_fixel_old.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + + +#include "command.h" +#include "progressbar.h" +#include "datatype.h" + + +#include "image.h" +#include "sparse/image.h" +#include "sparse/fixel_metric.h" +#include "image_helpers.h" +#include "algo/threaded_loop.h" +using MR::Sparse::FixelMetric; + +using namespace MR; +using namespace App; + +void usage () +{ + AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) and Robert E. Smith (robert.smith@florey.edu.au)"; + + DESCRIPTION + + "compare two fixel images for differences, within specified tolerance."; + + ARGUMENTS + + Argument ("data1", "a fixel image.").type_image_in () + + Argument ("data2", "another fixel image.").type_image_in () + + Argument ("tolerance", "the amount of signal difference to consider acceptable").type_float (0.0); +} + + +void run () +{ + Sparse::Image buffer1 (argument[0]); + Sparse::Image buffer2 (argument[1]); + check_dimensions (buffer1, buffer2); + for (size_t i = 0; i < buffer1.ndim(); ++i) { + if (std::isfinite (buffer1.spacing(i))) + if (buffer1.spacing(i) != buffer2.spacing(i)) + throw Exception ("images \"" + buffer1.name() + "\" and \"" + buffer2.name() + "\" do not have matching voxel spacings " + + str(buffer1.spacing(i)) + " vs " + str(buffer2.spacing(i))); + } + for (size_t i = 0; i < 3; ++i) { + for (size_t j = 0; j < 4; ++j) { + if (std::abs (buffer1.transform()(i,j) - buffer2.transform()(i,j)) > 0.001) + throw Exception ("images \"" + buffer1.name() + "\" and \"" + buffer2.name() + "\" do not have matching header transforms " + + "\n" + str(buffer1.transform().matrix()) + "vs \n " + str(buffer2.transform().matrix()) + ")"); + } + } + + double tol = argument[2]; + + ThreadedLoop (buffer1) + .run ([&tol] (decltype(buffer1)& a, decltype(buffer2)& b) { + if (a.value().size() != b.value().size()) + throw Exception ("the fixel images do not have corresponding fixels in all voxels"); + // For each fixel + for (size_t fixel = 0; fixel != a.value().size(); ++fixel) { + // Check value + if (std::abs (a.value()[fixel].value - b.value()[fixel].value) > tol) + throw Exception ("images \"" + a.name() + "\" and \"" + b.name() + "\" do not match fixel value within specified precision of " + str(tol) + + " (" + str(a.value()[fixel].value) + " vs " + str(b.value()[fixel].value) + ")"); + // Check size + if (std::abs (a.value()[fixel].size - b.value()[fixel].size) > tol) + throw Exception ("images \"" + a.name() + "\" and \"" + b.name() + "\" do not match fixel size within specified precision of " + str(tol) + + " (" + str(a.value()[fixel].size) + " vs " + str(b.value()[fixel].size) + ")"); + // Check Direction + for (size_t dim = 0; dim < 3; ++dim) { + if (std::abs (a.value()[fixel].dir[dim] - b.value()[fixel].dir[dim]) > tol) + throw Exception ("images \"" + a.name() + "\" and \"" + b.name() + "\" do not match fixel direction within specified precision of " + str(tol) + + " (" + str(a.value()[fixel].dir[dim]) + " vs " + str(b.value()[fixel].dir[dim]) + ")"); + } + } + }, buffer1, buffer2); + + + CONSOLE ("data checked OK"); +} diff --git a/testing/data b/testing/data index 92b6f44eab..ad3f1f3eb1 160000 --- a/testing/data +++ b/testing/data @@ -1 +1 @@ -Subproject commit 92b6f44eabd2372a19c62cb0a398d3f2640bc67a +Subproject commit ad3f1f3eb1d31d5d349956b0a1ab90b95a683c3d From 43f021f8ee1cd88103b460c99d1a8bbdac2881c2 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Tue, 15 Nov 2016 15:42:10 +1100 Subject: [PATCH 225/723] Added tests forgotten in previous commits --- testing/tests/fixel2voxel | 13 +++++++++++++ testing/tests/fixelconvert | 2 ++ testing/tests/fixelcrop | 1 + 3 files changed, 16 insertions(+) create mode 100644 testing/tests/fixel2voxel create mode 100644 testing/tests/fixelconvert create mode 100644 testing/tests/fixelcrop diff --git a/testing/tests/fixel2voxel b/testing/tests/fixel2voxel new file mode 100644 index 0000000000..dc8f7c7e64 --- /dev/null +++ b/testing/tests/fixel2voxel @@ -0,0 +1,13 @@ +fixel2voxel fixel_image/afd.mif min - | testing_diff_image - fixel2voxel/min.mif -frac 0.001 +fixel2voxel fixel_image/afd.mif mean - | testing_diff_image - fixel2voxel/mean.mif -frac 0.001 +fixel2voxel fixel_image/afd.mif sum - | testing_diff_image - fixel2voxel/sum.mif -frac 0.001 +fixel2voxel fixel_image/afd.mif product - | testing_diff_image - fixel2voxel/product.mif -frac 0.001 +fixel2voxel fixel_image/afd.mif absmax - | testing_diff_image - fixel2voxel/absmax.mif -frac 0.001 +fixel2voxel fixel_image/afd.mif magmax - | testing_diff_image - fixel2voxel/magmax.mif -frac 0.001 +fixel2voxel fixel_image/afd.mif count - | testing_diff_image - fixel2voxel/count.mif -frac 0.001 +fixel2voxel fixel_image/afd.mif complexity - | testing_diff_image - fixel2voxel/complexity.mif -frac 0.001 +fixel2voxel fixel_image/afd.mif sf - | testing_diff_image - fixel2voxel/sf.mif -frac 0.001 +fixel2voxel fixel_image/afd.mif dec_unit - | testing_diff_image - fixel2voxel/dec_unit.mif -frac 0.001 +fixel2voxel fixel_image/afd.mif dec_scaled - | testing_diff_image - fixel2voxel/dec_scaled.mif -frac 0.001 +fixel2voxel fixel_image/afd.mif split_data - | testing_diff_image - fixel2voxel/split_data.mif -frac 0.001 +fixel2voxel fixel_image/afd.mif split_dir - | testing_diff_image - fixel2voxel/split_dir.mif -frac 0.001 diff --git a/testing/tests/fixelconvert b/testing/tests/fixelconvert new file mode 100644 index 0000000000..f5b3ef5dfa --- /dev/null +++ b/testing/tests/fixelconvert @@ -0,0 +1,2 @@ +fixelconvert fixel_image tmp.msf -value fixel_image/afd.mif; testing_diff_fixel_old tmp.msf old_fixel.msf 0.001; rm tmp.msf -f +fixelconvert old_fixel.msf fixelconvert_tmp; testing_diff_fixel fixelconvert_tmp fixelconvert; rm -rf fixelconvert_tmp diff --git a/testing/tests/fixelcrop b/testing/tests/fixelcrop new file mode 100644 index 0000000000..0b3e67cdd0 --- /dev/null +++ b/testing/tests/fixelcrop @@ -0,0 +1 @@ +mrthreshold fixel_image/afd.mif -abs 0.08 - | fixelcrop fixel_image - fixelcrop_tmp; testing_diff_fixel fixelcrop_tmp fixelcrop -frac 0.001; rm -rf fixelcrop_tmp From 506fe87055c9dce5fca155b588b921ab8f91c53b Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Tue, 15 Nov 2016 16:17:56 +1100 Subject: [PATCH 226/723] Added tests for fixelreorient and fixelcorrespondence --- cmd/fixelreorient.cpp | 4 ++-- testing/data | 2 +- testing/tests/fixelcorrespondence | 1 + testing/tests/fixelreorient | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 testing/tests/fixelcorrespondence create mode 100644 testing/tests/fixelreorient diff --git a/cmd/fixelreorient.cpp b/cmd/fixelreorient.cpp index 0b7b38526c..d70a8d3145 100644 --- a/cmd/fixelreorient.cpp +++ b/cmd/fixelreorient.cpp @@ -42,8 +42,8 @@ void usage () "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 directorys are the same, the existing directions file will " - "be replaced (providing the --force option is supplied). If a new directory is supplied then all " - "fixel data will be copied to the new directory.").type_text (); + "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_text (); } diff --git a/testing/data b/testing/data index ad3f1f3eb1..aff56d4e3e 160000 --- a/testing/data +++ b/testing/data @@ -1 +1 @@ -Subproject commit ad3f1f3eb1d31d5d349956b0a1ab90b95a683c3d +Subproject commit aff56d4e3e5ba3f678899a3fc77e6a096b38b32b diff --git a/testing/tests/fixelcorrespondence b/testing/tests/fixelcorrespondence new file mode 100644 index 0000000000..48268aa451 --- /dev/null +++ b/testing/tests/fixelcorrespondence @@ -0,0 +1 @@ +fixelcorrespondence fixel_image/afd.mif fixel_image_template fixelcorrespondence_tmp afd.mif; testing_diff_fixel fixelcorrespondence_tmp fixelcorrespondence -frac 0.001; rm -rf fixelcorrespondence_tmp diff --git a/testing/tests/fixelreorient b/testing/tests/fixelreorient new file mode 100644 index 0000000000..4a386c66a2 --- /dev/null +++ b/testing/tests/fixelreorient @@ -0,0 +1 @@ +fixelreorient fixel_image fod_warp.mif fixelreorient_tmp -force; testing_diff_fixel fixelreorient_tmp fixelreorient -frac 0.001; rm fixelreorient_tmp -rf From 81cbc6ea44585c6500f1b5096b0e6d7b184a69b7 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Tue, 15 Nov 2016 16:29:19 +1100 Subject: [PATCH 227/723] Added tests for warp2metric --- testing/data | 2 +- testing/tests/warp2metric | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 testing/tests/warp2metric diff --git a/testing/data b/testing/data index aff56d4e3e..1fce3a91c3 160000 --- a/testing/data +++ b/testing/data @@ -1 +1 @@ -Subproject commit aff56d4e3e5ba3f678899a3fc77e6a096b38b32b +Subproject commit 1fce3a91c3d259d8433e987bf863ba34d40cefc0 diff --git a/testing/tests/warp2metric b/testing/tests/warp2metric new file mode 100644 index 0000000000..5d0d7cd66c --- /dev/null +++ b/testing/tests/warp2metric @@ -0,0 +1,3 @@ +warp2metric fod_warp.mif -jdet - | testing_diff_image - warp2metric/jdet.mif -frac 0.001 +warp2metric fod_warp.mif -jmat - | testing_diff_image - warp2metric/jmat.mif -frac 0.001 +warp2metric fod_warp.mif -fc fixel_image warp2metric_tmp fc.mif -force; testing_diff_fixel warp2metric_tmp warp2metric/fc -frac 0.001; rm -rf warp2metric_tmp From 7f1dfd4aa3f7b23213ebb511efdadd617212e978 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Tue, 15 Nov 2016 17:01:21 +0000 Subject: [PATCH 228/723] configure: detect allocator memory alignment, and set macro accordingly --- configure | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/configure b/configure index 6ede43b376..0e887b6353 100755 --- a/configure +++ b/configure @@ -753,15 +753,14 @@ except: # check Eigen3 memory alignment: -report('Checking Eigen 3 memory alignment requirements: ') +report('Detecting allocator memory alignment: ') try: - compile (''' + memalign = compile (''' #include -#include +#include int main (int argc, char* argv[]) { -#ifdef EIGEN_DEFAULT_ALIGN_BYTES size_t default_align = std::numeric_limits::max(); for (size_t n = 0; n < 100; ++n) { struct alignas(64) X { double c[8]; }; @@ -770,29 +769,26 @@ int main (int argc, char* argv[]) if (align && align < default_align) default_align = align; } - std::cout << "allocator memory alignment: " << default_align << ", Eigen3 needs: " << EIGEN_DEFAULT_ALIGN_BYTES << "\\n"; - - return default_align < EIGEN_DEFAULT_ALIGN_BYTES; -#else + std::cout << default_align << "\\n"; return 0; -#endif } -''', [ '-v' ] + cpp_flags, ld_flags) +''', cpp_flags, ld_flags) - report ('OK\n') + cpp_flags += [ '-DMRTRIX_ALLOC_MEMALIGN='+memalign ] + report (memalign + ' byte\n') except CompileError: - error (''' error compiling Eigen application! + error (''' error compiling application! This shouldn't happen!''' + configure_log_hint) except LinkError: - error (''' error linking Eigen application! + error (''' error linking application! This shouldn't happen!''' + configure_log_hint) except RuntimeError: - error (''' memory alignment of default allocator is not sufficient for Eigen3 to operate correctly! + error (''' error running application - Please report this to the MRtrix3 developer team, providing the full "configure.log" file\n''') + This shouldn't happen!''' + configure_log_hint) except: error ('unexpected exception!' + configure_log_hint) From 325d1ab9a51a744d1493330cb1874fbdfcc178ea Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Tue, 15 Nov 2016 17:01:46 +0000 Subject: [PATCH 229/723] remove all instances of EIGEN_MAKE_ALIGNED_OPERATOR_NEW --- lib/adapter/extract.h | 2 - lib/header.h | 1 - lib/image.h | 1 - lib/interp/base.h | 1 - lib/interp/cubic.h | 4 -- lib/interp/linear.h | 4 -- lib/interp/nearest.h | 1 - lib/interp/sinc.h | 1 - lib/transform.h | 1 - lib/types.h | 57 +++++++++++++++++++ src/dwi/fixel_map.h | 2 - src/dwi/tractography/ACT/method.h | 1 - .../tractography/mapping/gaussian/mapper.h | 1 - src/dwi/tractography/mapping/mapper.h | 2 - src/dwi/tractography/seeding/basic.h | 1 - src/registration/metric/cross_correlation.h | 1 - src/registration/nonlinear.h | 1 - src/registration/transform/affine.h | 1 - src/registration/transform/base.h | 1 - .../transform/initialiser_helpers.h | 2 - src/registration/transform/rigid.h | 1 - src/registration/transform/search.h | 1 - src/registration/warp/compose.h | 4 -- 23 files changed, 57 insertions(+), 35 deletions(-) diff --git a/lib/adapter/extract.h b/lib/adapter/extract.h index 7c01e0ab4f..48149eadc9 100644 --- a/lib/adapter/extract.h +++ b/lib/adapter/extract.h @@ -48,7 +48,6 @@ namespace MR this->indices.push_back (indices.back()); } - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; void reset () { for (size_t n = 0; n < ndim(); ++n) @@ -130,7 +129,6 @@ namespace MR } } - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; ssize_t size (size_t axis) const { return sizes[axis]; } diff --git a/lib/header.h b/lib/header.h index a24509c862..0c3b767fde 100644 --- a/lib/header.h +++ b/lib/header.h @@ -116,7 +116,6 @@ namespace MR } } - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; //! assignment operator /*! This copies everything over, resets the intensity scaling if the data diff --git a/lib/image.h b/lib/image.h index a391116223..a94a434b5d 100644 --- a/lib/image.h +++ b/lib/image.h @@ -232,7 +232,6 @@ namespace MR Buffer (const Buffer& b) : Header (b), fetch_func (b.fetch_func), store_func (b.store_func) { } - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; FORCE_INLINE ValueType get_value (size_t offset) const { ssize_t nseg = offset / io->segment_size(); diff --git a/lib/interp/base.h b/lib/interp/base.h index 56b27c9f81..4400c400dc 100644 --- a/lib/interp/base.h +++ b/lib/interp/base.h @@ -78,7 +78,6 @@ namespace MR bounds { parent.size(0) - 0.5, parent.size(1) - 0.5, parent.size(2) - 0.5 }, out_of_bounds (true) { } - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; //! Functions that must be defined by interpolation classes /*! The follwing functions must be defined by any derived diff --git a/lib/interp/cubic.h b/lib/interp/cubic.h index 38770fad5b..a847a3f9f3 100644 --- a/lib/interp/cubic.h +++ b/lib/interp/cubic.h @@ -76,7 +76,6 @@ namespace MR SplineInterpBase (const ImageType& parent, value_type value_when_out_of_bounds = Base::default_out_of_bounds_value()) : Base (parent, value_when_out_of_bounds), H { SplineType(PType), SplineType(PType), SplineType(PType) } { } - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; protected: SplineType H[3]; @@ -106,7 +105,6 @@ namespace MR { public: using SplineBase = SplineInterpBase; - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; typedef typename SplineBase::value_type value_type; using SplineBase::P; @@ -222,7 +220,6 @@ namespace MR { public: using SplineBase = SplineInterpBase; - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; typedef typename SplineBase::value_type value_type; using SplineBase::P; @@ -369,7 +366,6 @@ namespace MR { public: using SplineBase = SplineInterpBase; - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; typedef typename SplineBase::value_type value_type; using SplineBase::P; diff --git a/lib/interp/linear.h b/lib/interp/linear.h index 4ef9ef0e17..870c035b38 100644 --- a/lib/interp/linear.h +++ b/lib/interp/linear.h @@ -97,7 +97,6 @@ namespace MR using typename Base::value_type; typedef typename value_type_of::type coef_type; - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; LinearInterpBase (const ImageType& parent, value_type value_when_out_of_bounds = Base::default_out_of_bounds_value()) : Base (parent, value_when_out_of_bounds), @@ -132,7 +131,6 @@ namespace MR { public: using LinearBase = LinearInterpBase; - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; typedef typename LinearBase::value_type value_type; typedef typename LinearBase::coef_type coef_type; @@ -259,7 +257,6 @@ namespace MR { public: using LinearBase = LinearInterpBase; - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; typedef typename LinearBase::value_type value_type; typedef typename LinearBase::coef_type coef_type; @@ -409,7 +406,6 @@ namespace MR { public: using LinearBase = LinearInterpBase; - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; typedef typename LinearBase::value_type value_type; typedef typename LinearBase::coef_type coef_type; diff --git a/lib/interp/nearest.h b/lib/interp/nearest.h index 21321a83ce..2dc035b272 100644 --- a/lib/interp/nearest.h +++ b/lib/interp/nearest.h @@ -75,7 +75,6 @@ namespace MR Nearest (const ImageType& parent, value_type value_when_out_of_bounds = Base::default_out_of_bounds_value()) : Base (parent, value_when_out_of_bounds) { } - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; //! Set the current position to voxel space position \a pos /*! See file interp/base.h for details. */ diff --git a/lib/interp/sinc.h b/lib/interp/sinc.h index 4f2ba5823d..dcf2244f9b 100644 --- a/lib/interp/sinc.h +++ b/lib/interp/sinc.h @@ -86,7 +86,6 @@ namespace MR assert (w % 2); } - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; //! Set the current position to voxel space position \a pos /*! See file interp/base.h for details. */ diff --git a/lib/transform.h b/lib/transform.h index d5744e97a2..d2a9359d22 100644 --- a/lib/transform.h +++ b/lib/transform.h @@ -33,7 +33,6 @@ namespace MR image2scanner (header.transform()), scanner2image (image2scanner.inverse()) { } - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; Transform (const Transform&) = default; Transform (Transform&&) = default; diff --git a/lib/types.h b/lib/types.h index eb7a0cdb30..e54e97d169 100644 --- a/lib/types.h +++ b/lib/types.h @@ -108,6 +108,59 @@ # define FORCE_INLINE inline #endif + +/*! \def MEMALIGN(classname) + * to be placed immediately after \e every class or struct definition, to + * ensure the class had appropriate alignment-aware new & delete operators. + * This is required to ensure correct operation with Eigen >= 3.3 (due to the + * 32-byte alignment requirement for AVX instructions). + * + * A big problem with this is that \e every class that could contain + * fixed-sized Eigen types (e.g. Matrix4d) must have these operators defined in + * case any instances get allocated dynamically. The default new operator + * provides no alignment guarantees, even for types declared using alignas(). + * It is very difficult to keep track of all such classes to make sure they are + * all appropriately defined. The approach here will only enforce memory + * alignment for those class that declare themselves are requirement wider + * alignment than that provided by the default new operator (typically 16 byte + * with gcc), using the default allocator otherwise. This means it can be used + * for \e all classes indiscriminately with no loss of performance or memory + * use efficiency, and under certain conditions makes it possible to largely + * automate the process of ensuring compliance, as long as all class or struct + * definitions all on the same line, for example: + * \code + * template class AlignMe { MEMALIGN(AlignMe) + * ... + * }; + * \endcode + * \warning This will insert a \c public: declaration at the head of the class + * or struct. While this is the default for struct, special care must be taken + * with class declarations since methods would otherwise be private by default. + * If you need to have a private section at the head of your class, make sure + * you use an explicit \c private: or \c protected: keyword as required. + */ +#define MEMALIGN(classname) \ + public: \ + inline void* operator new (std::size_t size) { \ + if (alignof(classname) <= MRTRIX_ALLOC_MEMALIGN) return ::operator new (size); \ + std::cerr << __PRETTY_FUNCTION__ << ": new [" << size << "]\n"; \ + auto* original = std::malloc (size + alignof(classname)); \ + if (!original) throw std::bad_alloc(); \ + void *aligned = reinterpret_cast((reinterpret_cast(original) & ~(std::size_t(alignof(classname)-1))) + alignof(classname)); \ + *(reinterpret_cast(aligned) - 1) = original; \ + return aligned; \ + } \ + inline void* operator new[] (std::size_t size) { return classname::operator new (size); } \ + inline void operator delete(void* ptr) { \ + if (alignof(classname) <= MRTRIX_ALLOC_MEMALIGN) ::operator delete (ptr); \ + else { \ + std::cerr << __PRETTY_FUNCTION__ << ": delete\n"; \ + if (ptr) std::free (*(reinterpret_cast(ptr) - 1)); \ + } \ + } \ + inline void operator delete[](void* ptr, std::size_t sz) { classname::operator delete (ptr); } \ + + namespace MR { @@ -182,3 +235,7 @@ namespace Eigen { #endif + + + + diff --git a/src/dwi/fixel_map.h b/src/dwi/fixel_map.h index b75b7c0de9..d30b6b5a29 100644 --- a/src/dwi/fixel_map.h +++ b/src/dwi/fixel_map.h @@ -49,7 +49,6 @@ namespace MR fixels.push_back (Fixel()); } Fixel_map (const Fixel_map&) = delete; - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; class MapVoxel; typedef Image VoxelAccessor; @@ -126,7 +125,6 @@ namespace MR lookup_table = nullptr; } } - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; size_t first_index() const { return first_fixel_index; } size_t num_fixels() const { return count; } diff --git a/src/dwi/tractography/ACT/method.h b/src/dwi/tractography/ACT/method.h index f9ddb55c59..fbcb160e9d 100644 --- a/src/dwi/tractography/ACT/method.h +++ b/src/dwi/tractography/ACT/method.h @@ -52,7 +52,6 @@ namespace MR ACT_Method_additions (const ACT_Method_additions&) = delete; ACT_Method_additions() = delete; - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; const Tissues& tissues() const { return tissue_values; } diff --git a/src/dwi/tractography/mapping/gaussian/mapper.h b/src/dwi/tractography/mapping/gaussian/mapper.h index 0b8ca13d46..cccab34944 100644 --- a/src/dwi/tractography/mapping/gaussian/mapper.h +++ b/src/dwi/tractography/mapping/gaussian/mapper.h @@ -50,7 +50,6 @@ namespace MR { TrackMapper (const TrackMapper&) = default; ~TrackMapper() { } - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; void set_gaussian_FWHM (const float FWHM) { diff --git a/src/dwi/tractography/mapping/mapper.h b/src/dwi/tractography/mapping/mapper.h index daac3daf8b..a81d3c3f0c 100644 --- a/src/dwi/tractography/mapping/mapper.h +++ b/src/dwi/tractography/mapping/mapper.h @@ -79,7 +79,6 @@ namespace MR { TrackMapperBase (const TrackMapperBase&) = default; TrackMapperBase (TrackMapperBase&&) = default; - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; virtual ~TrackMapperBase() { } @@ -370,7 +369,6 @@ namespace MR { } } - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; void add_scalar_image (const std::string&); void add_fod_image (const std::string&); diff --git a/src/dwi/tractography/seeding/basic.h b/src/dwi/tractography/seeding/basic.h index 6395cc1071..19da650b37 100644 --- a/src/dwi/tractography/seeding/basic.h +++ b/src/dwi/tractography/seeding/basic.h @@ -143,7 +143,6 @@ namespace MR typedef Eigen::Transform transform_type; Rejection (const std::string&); - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; virtual bool get_seed (Eigen::Vector3f& p) const override; diff --git a/src/registration/metric/cross_correlation.h b/src/registration/metric/cross_correlation.h index def94b271d..e191e6ab93 100644 --- a/src/registration/metric/cross_correlation.h +++ b/src/registration/metric/cross_correlation.h @@ -84,7 +84,6 @@ namespace MR global_cnt += cnt; } - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; template void operator() (ProcessedImageType& pimage, MaskImageType& mask) { diff --git a/src/registration/nonlinear.h b/src/registration/nonlinear.h index d38c79fb13..dde2df1eae 100644 --- a/src/registration/nonlinear.h +++ b/src/registration/nonlinear.h @@ -62,7 +62,6 @@ namespace MR fod_lmax[2] = 4; } - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; template void run (TransformType linear_transform, diff --git a/src/registration/transform/affine.h b/src/registration/transform/affine.h index 91b97e2ce9..924f9cd101 100644 --- a/src/registration/transform/affine.h +++ b/src/registration/transform/affine.h @@ -86,7 +86,6 @@ namespace MR typedef AffineRobustEstimator RobustEstimatorType; typedef int has_robust_estimator; - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; Affine () : Base (12) { //CONF option: reg_gdweight_matrix diff --git a/src/registration/transform/base.h b/src/registration/transform/base.h index 832d8d485d..89a50c93a6 100644 --- a/src/registration/transform/base.h +++ b/src/registration/transform/base.h @@ -93,7 +93,6 @@ namespace MR centre.setZero(); } - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; template inline void transform (OutPointType& out, const InPointType& in) const { diff --git a/src/registration/transform/initialiser_helpers.h b/src/registration/transform/initialiser_helpers.h index b67e63ce32..3fa8dbdca0 100644 --- a/src/registration/transform/initialiser_helpers.h +++ b/src/registration/transform/initialiser_helpers.h @@ -92,7 +92,6 @@ namespace MR void run (); - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3 protected: void init (Image& image, @@ -128,7 +127,6 @@ namespace MR void run (); - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3 protected: bool calculate_eigenvectors ( diff --git a/src/registration/transform/rigid.h b/src/registration/transform/rigid.h index e975f4d71b..a6313fa6ca 100644 --- a/src/registration/transform/rigid.h +++ b/src/registration/transform/rigid.h @@ -68,7 +68,6 @@ namespace MR class Rigid : public Base { public: - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; typedef typename Base::ParameterType ParameterType; typedef RigidLinearNonSymmetricUpdate UpdateType; diff --git a/src/registration/transform/search.h b/src/registration/transform/search.h index 263ac579ff..4c764490b6 100644 --- a/src/registration/transform/search.h +++ b/src/registration/transform/search.h @@ -87,7 +87,6 @@ namespace MR local_trafo.set_translation (offset); }; - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; typedef Metric::Params, diff --git a/src/registration/warp/compose.h b/src/registration/warp/compose.h index b9ba296cd8..7bc9a14612 100644 --- a/src/registration/warp/compose.h +++ b/src/registration/warp/compose.h @@ -36,7 +36,6 @@ namespace MR ComposeLinearDeformKernel (const transform_type& transform) : transform (transform) {} - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; template void operator() (InputDeformationFieldType& deform_input, OutputDeformationFieldType& deform_output) { @@ -55,7 +54,6 @@ namespace MR linear_transform (linear_transform), image_transform (disp_in) {} - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; template void operator() (DisplacementFieldType& disp_input, DeformationFieldType& deform_output) { @@ -73,7 +71,6 @@ namespace MR ComposeDispKernel (Image& disp_input1, Image& disp_input2, default_type step) : disp1_transform (disp_input1), disp2_interp (disp_input2), step (step) {} - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; void operator() (Image& disp_input1, Image& disp_output) { Eigen::Vector3 voxel ((default_type)disp_input1.index(0), (default_type)disp_input1.index(1), (default_type)disp_input1.index(2)); @@ -106,7 +103,6 @@ namespace MR out_of_bounds *= NaN; } - EIGEN_MAKE_ALIGNED_OPERATOR_NEW // avoid memory alignment errors in Eigen3; void operator() (Image& deform) { Eigen::Vector3 voxel ((default_type)deform.index(0), (default_type)deform.index(1), (default_type)deform.index(2)); From 0ff7d99e9ed262542148405db71003bbf4545004 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 16 Nov 2016 13:05:12 +1100 Subject: [PATCH 230/723] added some generated test files to .gitignore --- .gitignore | 2 ++ docs/reference/commands/fixelreorient.rst | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d3aaf67511..d3578b2a39 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,6 @@ icons.cpp testing.log testing/build.log testing/release/ +testing/src/project_version.h +testing/build .__tmp.log diff --git a/docs/reference/commands/fixelreorient.rst b/docs/reference/commands/fixelreorient.rst index 792604f622..629f70747b 100644 --- a/docs/reference/commands/fixelreorient.rst +++ b/docs/reference/commands/fixelreorient.rst @@ -12,7 +12,7 @@ Synopsis - *fixel_in*: the fixel directory - *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 -- *fixel_out*: the output fixel directory. If the the input and output directorys are the same, the existing directions file will be replaced (providing the --force option is supplied). If a new directory is supplied then all fixel data will be copied to the new directory. +- *fixel_out*: the output fixel directory. If the the input and output directorys 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. Description ----------- From eadb98a0124fdb87b3fd781e756531124d3870a2 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Wed, 16 Nov 2016 14:10:29 +1100 Subject: [PATCH 231/723] Moved old sparse image format to lib/sparse/legacy. Move /lib/fixel_format to /lib/sparse --- cmd/fixel2sh.cpp | 14 +- cmd/fixel2tsf.cpp | 10 +- cmd/fixel2voxel.cpp | 46 ++--- cmd/fixelcfestats.cpp | 20 +-- cmd/fixelconvert.cpp | 46 ++--- cmd/fixelcorrespondence.cpp | 16 +- cmd/fixelcrop.cpp | 18 +- cmd/fixelreorient.cpp | 16 +- cmd/fod2fixel.cpp | 16 +- cmd/tck2fixel.cpp | 18 +- cmd/voxel2fixel.cpp | 16 +- cmd/warp2metric.cpp | 18 +- ...ix_sparse.cpp => mrtrix_sparse_legacy.cpp} | 18 +- lib/image_io/sparse.cpp | 6 +- lib/image_io/sparse.h | 8 +- lib/sparse/fixel_metric.h | 59 ------- lib/{fixel_format => sparse}/helpers.h | 36 ++-- lib/sparse/image.h | 154 ----------------- lib/sparse/keys.h | 21 +-- lib/sparse/legacy/fixel_metric.h | 60 +++++++ lib/sparse/legacy/image.h | 157 ++++++++++++++++++ lib/{fixel_format => sparse/legacy}/keys.h | 27 ++- lib/{fixel_format => sparse}/loop.h | 6 +- src/dwi/tractography/SIFT/output.h | 32 ++-- src/dwi/tractography/SIFT/sifter.cpp | 2 +- src/dwi/tractography/SIFT2/tckfactor.cpp | 28 ++-- src/dwi/tractography/seeding/dynamic.cpp | 30 ++-- src/gui/mrview/tool/vector/fixel.h | 8 +- src/gui/mrview/tool/vector/fixelfolder.cpp | 6 +- src/gui/mrview/tool/vector/fixelfolder.h | 2 +- testing/cmd/testing_diff_fixel.cpp | 6 +- testing/cmd/testing_diff_fixel_old.cpp | 6 +- 32 files changed, 465 insertions(+), 461 deletions(-) rename lib/formats/{mrtrix_sparse.cpp => mrtrix_sparse_legacy.cpp} (85%) delete mode 100644 lib/sparse/fixel_metric.h rename lib/{fixel_format => sparse}/helpers.h (92%) delete mode 100644 lib/sparse/image.h create mode 100644 lib/sparse/legacy/fixel_metric.h create mode 100644 lib/sparse/legacy/image.h rename lib/{fixel_format => sparse/legacy}/keys.h (59%) rename lib/{fixel_format => sparse}/loop.h (96%) diff --git a/cmd/fixel2sh.cpp b/cmd/fixel2sh.cpp index 4b91207473..b2c17eeb0a 100644 --- a/cmd/fixel2sh.cpp +++ b/cmd/fixel2sh.cpp @@ -22,9 +22,9 @@ #include "math/SH.h" -#include "fixel_format/helpers.h" -#include "fixel_format/keys.h" -#include "fixel_format/loop.h" +#include "sparse/helpers.h" +#include "sparse/keys.h" +#include "sparse/loop.h" using namespace MR; using namespace App; @@ -53,11 +53,11 @@ void usage () void run () { - auto in_data_image = FixelFormat::open_fixel_data_file (argument[0]); + auto in_data_image = Sparse::open_fixel_data_file (argument[0]); - Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_directory (argument[0])); + Header in_index_header = Sparse::find_index_header (Sparse::get_fixel_directory (argument[0])); auto in_index_image =in_index_header.get_image(); - auto in_directions_image = FixelFormat::find_directions_header (FixelFormat::get_fixel_directory (argument[0])).get_image().with_direct_io(); + auto in_directions_image = Sparse::find_directions_header (Sparse::get_fixel_directory (argument[0])).get_image().with_direct_io(); size_t lmax = 8; auto opt = get_options ("lmax"); @@ -78,7 +78,7 @@ void run () 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 = FixelFormat::FixelLoop (in_index_image) (in_directions_image, in_data_image); f; ++f) { + for (auto f = Sparse::FixelLoop (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) diff --git a/cmd/fixel2tsf.cpp b/cmd/fixel2tsf.cpp index 7d06334cfb..c3b7a1b33a 100644 --- a/cmd/fixel2tsf.cpp +++ b/cmd/fixel2tsf.cpp @@ -19,8 +19,8 @@ #include "algo/loop.h" #include "image.h" -#include "fixel_format/helpers.h" -#include "fixel_format/keys.h" +#include "sparse/helpers.h" +#include "sparse/keys.h" #include "dwi/tractography/file.h" #include "dwi/tractography/scalar_file.h" @@ -65,14 +65,14 @@ typedef DWI::Tractography::Mapping::SetVoxelDir SetVoxelDir; void run () { - auto in_data_image = FixelFormat::open_fixel_data_file (argument[0]); + auto in_data_image = Sparse::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"); - Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_directory (argument[0])); + Header in_index_header = Sparse::find_index_header (Sparse::get_fixel_directory (argument[0])); auto in_index_image = in_index_header.get_image(); - auto in_directions_image = FixelFormat::find_directions_header (FixelFormat::get_fixel_directory (argument[0])).get_image().with_direct_io(); + auto in_directions_image = Sparse::find_directions_header (Sparse::get_fixel_directory (argument[0])).get_image().with_direct_io(); DWI::Tractography::Properties properties; DWI::Tractography::Reader reader (argument[1], properties); diff --git a/cmd/fixel2voxel.cpp b/cmd/fixel2voxel.cpp index 1aa3c77959..ce08472210 100644 --- a/cmd/fixel2voxel.cpp +++ b/cmd/fixel2voxel.cpp @@ -26,9 +26,9 @@ #include "math/SH.h" -#include "fixel_format/helpers.h" -#include "fixel_format/keys.h" -#include "fixel_format/loop.h" +#include "sparse/helpers.h" +#include "sparse/keys.h" +#include "sparse/loop.h" using namespace MR; using namespace App; @@ -99,7 +99,7 @@ class Mean default_type sum = 0.0; default_type sum_volumes = 0.0; if (vol.valid()) { - for (auto f = FixelFormat::FixelLoop (index) (data, vol); f; ++f) { + for (auto f = Sparse::FixelLoop (index) (data, vol); f; ++f) { sum += data.value() * vol.value(); sum_volumes += vol.value(); } @@ -107,7 +107,7 @@ class Mean } else { index.index(3) = 0; size_t num_fixels = index.value(); - for (auto f = FixelFormat::FixelLoop (index) (data); f; ++f) + for (auto f = Sparse::FixelLoop (index) (data); f; ++f) sum += data.value(); out.value() = num_fixels ? (sum / default_type(num_fixels)) : 0.0; } @@ -127,10 +127,10 @@ class Sum { if (vol.valid()) { - for (auto f = FixelFormat::FixelLoop (index) (data, vol); f; ++f) + for (auto f = Sparse::FixelLoop (index) (data, vol); f; ++f) out.value() += data.value() * vol.value(); } else { - for (auto f = FixelFormat::FixelLoop (index) (data); f; ++f) + for (auto f = Sparse::FixelLoop (index) (data); f; ++f) out.value() += data.value(); } } @@ -176,7 +176,7 @@ class Min void operator() (Image& index, Image& out) { default_type min = std::numeric_limits::infinity(); - for (auto f = FixelFormat::FixelLoop (index) (data); f; ++f) { + for (auto f = Sparse::FixelLoop (index) (data); f; ++f) { if (data.value() < min) min = data.value(); } @@ -196,7 +196,7 @@ class Max void operator() (Image& index, Image& out) { default_type max = -std::numeric_limits::infinity(); - for (auto f = FixelFormat::FixelLoop (index) (data); f; ++f) { + for (auto f = Sparse::FixelLoop (index) (data); f; ++f) { if (data.value() > max) max = data.value(); } @@ -216,7 +216,7 @@ class AbsMax void operator() (Image& index, Image& out) { default_type absmax = -std::numeric_limits::infinity(); - for (auto f = FixelFormat::FixelLoop (index) (data); f; ++f) { + for (auto f = Sparse::FixelLoop (index) (data); f; ++f) { if (std::abs (data.value()) > absmax) absmax = std::abs (data.value()); } @@ -236,7 +236,7 @@ class MagMax void operator() (Image& index, Image& out) { default_type magmax = 0.0; - for (auto f = FixelFormat::FixelLoop (index) (data); f; ++f) { + for (auto f = Sparse::FixelLoop (index) (data); f; ++f) { if (std::abs (data.value()) > std::abs (magmax)) magmax = data.value(); } @@ -263,7 +263,7 @@ class Complexity } default_type max = 0.0; default_type sum = 0.0; - for (auto f = FixelFormat::FixelLoop (index) (data); f; ++f) { + for (auto f = Sparse::FixelLoop (index) (data); f; ++f) { max = std::max (max, default_type(data.value())); sum += data.value(); } @@ -283,7 +283,7 @@ class SF { default_type max = 0.0; default_type sum = 0.0; - for (auto f = FixelFormat::FixelLoop (index) (data); f; ++f) { + for (auto f = Sparse::FixelLoop (index) (data); f; ++f) { max = std::max (max, default_type(data.value())); sum += data.value(); } @@ -304,10 +304,10 @@ class DEC_unit { Eigen::Vector3 sum_dec = {0.0, 0.0, 0.0}; if (vol.valid()) { - for (auto f = FixelFormat::FixelLoop (index) (data, vol, dir); f; ++f) + for (auto f = Sparse::FixelLoop (index) (data, vol, dir); f; ++f) sum_dec += Eigen::Vector3 (std::abs (dir.row(1)[0]), std::abs (dir.row(1)[1]), std::abs (dir.row(1)[2])) * data.value() * vol.value(); } else { - for (auto f = FixelFormat::FixelLoop (index) (data, dir); f; ++f) + for (auto f = Sparse::FixelLoop (index) (data, dir); f; ++f) sum_dec += Eigen::Vector3 (std::abs (dir.row(1)[0]), std::abs (dir.row(1)[1]), std::abs (dir.row(1)[2])) * data.value(); } if ((sum_dec.array() != 0.0).any()) @@ -332,7 +332,7 @@ class DEC_scaled default_type sum_value = 0.0; if (vol.valid()) { default_type sum_volume = 0.0; - for (auto f = FixelFormat::FixelLoop (index) (data, vol, dir); f; ++f) { + for (auto f = Sparse::FixelLoop (index) (data, vol, dir); f; ++f) { sum_dec += Eigen::Vector3 (std::abs (dir.row(1)[0]), std::abs (dir.row(1)[1]), std::abs (dir.row(1)[2])) * data.value() * vol.value(); sum_volume += vol.value(); sum_value += vol.value() * data.value(); @@ -341,7 +341,7 @@ class DEC_scaled sum_dec.normalize(); sum_dec *= (sum_value / sum_volume); } else { - for (auto f = FixelFormat::FixelLoop (index) (data, dir); f; ++f) { + for (auto f = Sparse::FixelLoop (index) (data, dir); f; ++f) { sum_dec += Eigen::Vector3 (std::abs (dir.row(1)[0]), std::abs (dir.row(1)[1]), std::abs (dir.row(1)[2])) * data.value(); sum_value += data.value(); } @@ -390,7 +390,7 @@ class SplitDir void operator() (Image& index, Image& out) { out.index(3) = 0; - for (auto f = FixelFormat::FixelLoop (index) (dir); f; ++f) { + for (auto f = Sparse::FixelLoop (index) (dir); f; ++f) { for (size_t axis = 0; axis < 3; ++axis) { dir.index(1) = axis; out.value() = dir.value(); @@ -412,11 +412,11 @@ class SplitDir void run () { - auto in_data = FixelFormat::open_fixel_data_file (argument[0]); + auto in_data = Sparse::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)"); - Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_directory (argument[0])); + Header in_index_header = Sparse::find_index_header (Sparse::get_fixel_directory (argument[0])); auto in_index_image = in_index_header.get_image(); Image in_directions; @@ -426,7 +426,7 @@ void run () Header H_out (in_index_header); H_out.datatype() = DataType::Float32; H_out.datatype().set_byte_order_native(); - H_out.keyval().erase (FixelFormat::n_fixels_key); + H_out.keyval().erase (Sparse::n_fixels_key); if (op == 7) { // count H_out.datatype() = DataType::UInt8; } else if (op == 10 || op == 11) { // dec @@ -445,8 +445,8 @@ void run () } if (op == 10 || op == 11 || op == 13) // dec or split_dir - in_directions = FixelFormat::find_directions_header ( - FixelFormat::get_fixel_directory (in_data.name())).get_image().with_direct_io(); + in_directions = Sparse::find_directions_header ( + Sparse::get_fixel_directory (in_data.name())).get_image().with_direct_io(); Image in_vol; auto opt = get_options ("weighted"); diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index db22491216..2788e19df3 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -19,9 +19,9 @@ #include "algo/loop.h" #include "transform.h" #include "image.h" -#include "fixel_format/helpers.h" -#include "fixel_format/keys.h" -#include "fixel_format/loop.h" +#include "sparse/helpers.h" +#include "sparse/keys.h" +#include "sparse/loop.h" #include "math/stats/glm.h" #include "math/stats/permutation.h" #include "math/stats/typedefs.h" @@ -148,20 +148,20 @@ void run() { const std::string input_fixel_directory = argument[0]; - Header index_header = FixelFormat::find_index_header (input_fixel_directory); + Header index_header = Sparse::find_index_header (input_fixel_directory); auto index_image = index_header.get_image(); - const uint32_t num_fixels = FixelFormat::get_number_of_fixels (index_header); + const uint32_t num_fixels = Sparse::get_number_of_fixels (index_header); CONSOLE ("number of fixels: " + str(num_fixels)); std::vector positions (num_fixels); std::vector directions (num_fixels); const std::string output_fixel_directory = argument[5]; - FixelFormat::copy_index_and_directions_file (input_fixel_directory, output_fixel_directory); + Sparse::copy_index_and_directions_file (input_fixel_directory, output_fixel_directory); { - auto directions_data = FixelFormat::find_directions_header (input_fixel_directory).get_image().with_direct_io ({+2,+1}); + auto directions_data = Sparse::find_directions_header (input_fixel_directory).get_image().with_direct_io ({+2,+1}); // 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) { @@ -169,7 +169,7 @@ void run() { index_image.index(3) = 1; uint32_t offset = index_image.value(); size_t fixel_index = 0; - for (auto f = FixelFormat::FixelLoop (index_image) (directions_data); f; ++f, ++fixel_index) { + for (auto f = Sparse::FixelLoop (index_image) (directions_data); f; ++f, ++fixel_index) { directions[offset + fixel_index] = directions_data.row(1); positions[offset + fixel_index] = image_transform.voxel2scanner * vox; } @@ -190,7 +190,7 @@ void run() { if (!MR::Path::exists (filename)) throw Exception ("input fixel image not found: " + filename); header = Header::open (filename); - FixelFormat::fixels_match (index_header, header); + Sparse::fixels_match (index_header, header); identifiers.push_back (filename); progress++; } @@ -332,7 +332,7 @@ void run() { index_image.index(3) = 1; uint32_t offset = index_image.value(); uint32_t fixel_index = 0; - for (auto f = FixelFormat::FixelLoop (index_image) (subject_data); f; ++f, ++fixel_index) { + for (auto f = Sparse::FixelLoop (index_image) (subject_data); f; ++f, ++fixel_index) { if (!std::isfinite(subject_data.value())) throw Exception ("subject data file " + identifiers[subject] + " contains non-finite value: " + str(subject_data.value())); subject_data_vector[offset + fixel_index] = subject_data.value(); diff --git a/cmd/fixelconvert.cpp b/cmd/fixelconvert.cpp index 8094148cc7..686103f160 100644 --- a/cmd/fixelconvert.cpp +++ b/cmd/fixelconvert.cpp @@ -22,19 +22,19 @@ #include "math/SH.h" -#include "fixel_format/helpers.h" -#include "fixel_format/keys.h" -#include "fixel_format/loop.h" - -#include "sparse/fixel_metric.h" +#include "sparse/helpers.h" #include "sparse/keys.h" -#include "sparse/image.h" +#include "sparse/loop.h" + +#include "sparse/legacy/fixel_metric.h" +#include "sparse/legacy/keys.h" +#include "sparse/legacy/image.h" using namespace MR; using namespace App; -using Sparse::FixelMetric; +using Sparse::Legacy::FixelMetric; void usage () @@ -72,10 +72,10 @@ void usage () void convert_old2new () { Header header (Header::open (argument[0])); - header.keyval().erase (Sparse::name_key); - header.keyval().erase (Sparse::size_key); + header.keyval().erase (Sparse::Legacy::name_key); + header.keyval().erase (Sparse::Legacy::size_key); - Sparse::Image input (argument[0]); + Sparse::Legacy::Image input (argument[0]); const std::string file_extension = get_options ("nii").size() ? ".nii" : ".mif"; @@ -87,7 +87,7 @@ void convert_old2new () const bool output_size = get_options ("out_size").size(); const std::string output_fixel_directory = argument[1]; - FixelFormat::check_fixel_directory (output_fixel_directory, true); + Sparse::check_fixel_directory (output_fixel_directory, true); uint32_t fixel_count = 0; for (auto i = Loop (input) (input); i; ++i) @@ -104,7 +104,7 @@ void convert_old2new () Header directions_header (data_header); directions_header.size(1) = 3; - header.keyval()[FixelFormat::n_fixels_key] = str(fixel_count); + header.keyval()[Sparse::n_fixels_key] = str(fixel_count); header.ndim() = 4; header.size(3) = 2; header.datatype() = DataType::from(); @@ -121,10 +121,10 @@ void convert_old2new () Image template_directions_image; opt = get_options ("template"); if (opt.size()) { - FixelFormat::check_fixel_directory (opt[0][0]); - template_index_image = FixelFormat::find_index_header (opt[0][0]).get_image(); + Sparse::check_fixel_directory (opt[0][0]); + template_index_image = Sparse::find_index_header (opt[0][0]).get_image(); check_dimensions (index_image, template_index_image); - template_directions_image = FixelFormat::find_directions_header (opt[0][0]).get_image(); + template_directions_image = Sparse::find_directions_header (opt[0][0]).get_image(); } uint32_t offset = 0; @@ -181,9 +181,9 @@ void convert_new2old () opt = get_options ("in_size"); const std::string size_path = opt.size() ? std::string(opt[0][0]) : ""; - Header H_index = FixelFormat::find_index_header (input_fixel_directory); - Header H_dirs = FixelFormat::find_directions_header (input_fixel_directory); - std::vector
H_data = FixelFormat::find_data_headers (input_fixel_directory, H_index, false); + Header H_index = Sparse::find_index_header (input_fixel_directory); + Header H_dirs = Sparse::find_directions_header (input_fixel_directory); + std::vector
H_data = Sparse::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) { @@ -199,9 +199,9 @@ void convert_new2old () H_out.ndim() = 3; H_out.datatype() = DataType::UInt64; H_out.datatype().set_byte_order_native(); - H_out.keyval()[Sparse::name_key] = str(typeid(FixelMetric).name()); - H_out.keyval()[Sparse::size_key] = str(sizeof(FixelMetric)); - Sparse::Image out_image (argument[1], H_out); + H_out.keyval()[Sparse::Legacy::name_key] = str(typeid(FixelMetric).name()); + H_out.keyval()[Sparse::Legacy::size_key] = str(sizeof(FixelMetric)); + Sparse::Legacy::Image out_image (argument[1], H_out); auto index_image = H_index.get_image(); auto dirs_image = H_dirs.get_image(); @@ -214,14 +214,14 @@ void convert_new2old () index_image.index(3) = 0; const uint32_t num_fixels = index_image.value(); out_image.value().set_size (num_fixels); - for (auto f = FixelFormat::FixelLoop (index_image) (dirs_image, value_image); f; ++f) { + for (auto f = Sparse::FixelLoop (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(); } - Sparse::FixelMetric fixel (dir, value_image.value(), value_image.value()); + Sparse::Legacy::FixelMetric fixel (dir, value_image.value(), value_image.value()); if (size_image.valid()) { assign_pos_of (value_image).to (size_image); fixel.size = size_image.value(); diff --git a/cmd/fixelcorrespondence.cpp b/cmd/fixelcorrespondence.cpp index 7f8f996325..2e151eb4a5 100644 --- a/cmd/fixelcorrespondence.cpp +++ b/cmd/fixelcorrespondence.cpp @@ -18,8 +18,8 @@ #include "progressbar.h" #include "algo/loop.h" #include "image.h" -#include "fixel_format/helpers.h" -#include "fixel_format/keys.h" +#include "sparse/helpers.h" +#include "sparse/keys.h" using namespace MR; using namespace App; @@ -56,21 +56,21 @@ void run () 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 = FixelFormat::find_index_header (FixelFormat::get_fixel_directory (input_file)).get_image(); - auto subject_directions = FixelFormat::find_directions_header (FixelFormat::get_fixel_directory (input_file)).get_image().with_direct_io(); + auto subject_index = Sparse::find_index_header (Sparse::get_fixel_directory (input_file)).get_image(); + auto subject_directions = Sparse::find_directions_header (Sparse::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"); auto subject_data = Image::open (input_file); - FixelFormat::check_fixel_size (subject_index, subject_data); + Sparse::check_fixel_size (subject_index, subject_data); - auto template_index = FixelFormat::find_index_header (argument[1]).get_image(); - auto template_directions = FixelFormat::find_directions_header (argument[1]).get_image().with_direct_io(); + auto template_index = Sparse::find_index_header (argument[1]).get_image(); + auto template_directions = Sparse::find_directions_header (argument[1]).get_image().with_direct_io(); check_dimensions (subject_index, template_index); std::string output_fixel_directory = argument[2]; - FixelFormat::copy_index_and_directions_file (argument[1], output_fixel_directory); + Sparse::copy_index_and_directions_file (argument[1], output_fixel_directory); Header output_data_header (template_directions); output_data_header.size(1) = 1; diff --git a/cmd/fixelcrop.cpp b/cmd/fixelcrop.cpp index 47658d917a..68400ae014 100644 --- a/cmd/fixelcrop.cpp +++ b/cmd/fixelcrop.cpp @@ -20,8 +20,8 @@ #include "image.h" -#include "fixel_format/helpers.h" -#include "fixel_format/keys.h" +#include "sparse/helpers.h" +#include "sparse/keys.h" using namespace MR; using namespace App; @@ -48,18 +48,18 @@ void usage () void run () { const auto in_directory = argument[0]; - FixelFormat::check_fixel_directory (in_directory); - Header in_index_header = FixelFormat::find_index_header (in_directory); + Sparse::check_fixel_directory (in_directory); + Header in_index_header = Sparse::find_index_header (in_directory); auto in_index_image = in_index_header.get_image (); auto mask_image = Image::open (argument[1]); - FixelFormat::check_fixel_size (in_index_image, mask_image); + Sparse::check_fixel_size (in_index_image, mask_image); const auto out_fixel_directory = argument[2]; - FixelFormat::check_fixel_directory (out_fixel_directory, true); + Sparse::check_fixel_directory (out_fixel_directory, true); Header out_header = Header (in_index_image); - size_t total_nfixels = std::stoul (out_header.keyval ()[FixelFormat::n_fixels_key]); + size_t total_nfixels = std::stoul (out_header.keyval ()[Sparse::n_fixels_key]); // 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) { @@ -67,12 +67,12 @@ void run () total_nfixels --; } - out_header.keyval ()[FixelFormat::n_fixels_key] = str (total_nfixels); + out_header.keyval ()[Sparse::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 - std::vector
in_headers = FixelFormat::find_data_headers (in_directory, in_index_header, true); + std::vector
in_headers = Sparse::find_data_headers (in_directory, in_index_header, true); std::vector > in_data_images; std::vector > out_data_images; for (auto& in_data_header : in_headers) { diff --git a/cmd/fixelreorient.cpp b/cmd/fixelreorient.cpp index d70a8d3145..8f139f46f8 100644 --- a/cmd/fixelreorient.cpp +++ b/cmd/fixelreorient.cpp @@ -24,8 +24,8 @@ using namespace MR; using namespace App; -#include "fixel_format/helpers.h" -#include "fixel_format/keys.h" +#include "sparse/helpers.h" +#include "sparse/keys.h" void usage () { @@ -50,9 +50,9 @@ void usage () void run () { std::string input_fixel_directory = argument[0]; - FixelFormat::check_fixel_directory (input_fixel_directory); + Sparse::check_fixel_directory (input_fixel_directory); - auto input_index_image = FixelFormat::find_index_header (input_fixel_directory).get_image (); + auto input_index_image = Sparse::find_index_header (input_fixel_directory).get_image (); Header warp_header = Header::open (argument[1]); Registration::Warp::check_warp (warp_header); @@ -60,13 +60,13 @@ void run () Adapter::Jacobian > jacobian (warp_header.get_image()); std::string output_fixel_directory = argument[2]; - FixelFormat::check_fixel_directory (output_fixel_directory, true); + Sparse::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 = FixelFormat::find_directions_header (input_fixel_directory).get_image(); + auto tmp = Sparse::find_directions_header (input_fixel_directory).get_image(); input_directions_image = Image::scratch(tmp); threaded_copy (tmp, input_directions_image); output_directions_filename = Path::basename(tmp.name()); @@ -91,8 +91,8 @@ void run () } if (output_fixel_directory != input_fixel_directory) { - FixelFormat::copy_index_file (input_fixel_directory, output_fixel_directory); - FixelFormat::copy_all_data_files (input_fixel_directory, output_fixel_directory); + Sparse::copy_index_file (input_fixel_directory, output_fixel_directory); + Sparse::copy_all_data_files (input_fixel_directory, output_fixel_directory); } } diff --git a/cmd/fod2fixel.cpp b/cmd/fod2fixel.cpp index 00ebab5644..a985807baf 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -19,8 +19,8 @@ #include "image.h" -#include "fixel_format/keys.h" -#include "fixel_format/helpers.h" +#include "sparse/keys.h" +#include "sparse/helpers.h" #include "math/SH.h" @@ -201,7 +201,7 @@ void Segmented_FOD_receiver::commit () std::unique_ptr disp_image (nullptr); auto index_header (H); - index_header.keyval()[FixelFormat::n_fixels_key] = str(n_fixels); + index_header.keyval()[Sparse::n_fixels_key] = str(n_fixels); index_header.ndim() = 4; index_header.size(3) = 2; index_header.datatype() = DataType::from(); @@ -220,7 +220,7 @@ void Segmented_FOD_receiver::commit () dir_header.size(1) = 3; dir_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_directory_path, dir_path), dir_header))); dir_image->index(1) = 0; - FixelFormat::check_fixel_size (*index_image, *dir_image); + Sparse::check_fixel_size (*index_image, *dir_image); } if (afd_path.size()) { @@ -228,7 +228,7 @@ void Segmented_FOD_receiver::commit () afd_header.size(1) = 1; afd_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_directory_path, afd_path), afd_header))); afd_image->index(1) = 0; - FixelFormat::check_fixel_size (*index_image, *afd_image); + Sparse::check_fixel_size (*index_image, *afd_image); } if (peak_path.size()) { @@ -236,7 +236,7 @@ void Segmented_FOD_receiver::commit () peak_header.size(1) = 1; peak_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_directory_path, peak_path), peak_header))); peak_image->index(1) = 0; - FixelFormat::check_fixel_size (*index_image, *peak_image); + Sparse::check_fixel_size (*index_image, *peak_image); } if (disp_path.size()) { @@ -244,7 +244,7 @@ void Segmented_FOD_receiver::commit () disp_header.size(1) = 1; disp_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_directory_path, disp_path), disp_header))); disp_image->index(1) = 0; - FixelFormat::check_fixel_size (*index_image, *disp_image); + Sparse::check_fixel_size (*index_image, *disp_image); } size_t offset (0), lobe_index (0); @@ -337,7 +337,7 @@ void run () if (!receiver.num_outputs ()) throw Exception ("Nothing to do; please specify at least one output image type"); - FixelFormat::check_fixel_directory (fixel_directory_path, true, true); + Sparse::check_fixel_directory (fixel_directory_path, true, true); FMLS::FODQueueWriter writer (fod_data, mask); diff --git a/cmd/tck2fixel.cpp b/cmd/tck2fixel.cpp index 57730506bf..caf5012c5d 100644 --- a/cmd/tck2fixel.cpp +++ b/cmd/tck2fixel.cpp @@ -19,9 +19,9 @@ #include "algo/loop.h" #include "image.h" -#include "fixel_format/helpers.h" -#include "fixel_format/keys.h" -#include "fixel_format/loop.h" +#include "sparse/helpers.h" +#include "sparse/keys.h" +#include "sparse/loop.h" #include "dwi/tractography/mapping/mapper.h" #include "dwi/tractography/mapping/loader.h" @@ -126,10 +126,10 @@ void write_fixel_output (const std::string& filename, void run () { const std::string input_fixel_folder = argument[1]; - Header index_header = FixelFormat::find_index_header (input_fixel_folder); + Header index_header = Sparse::find_index_header (input_fixel_folder); auto index_image = index_header.get_image(); - const uint32_t num_fixels = FixelFormat::get_number_of_fixels (index_header); + const uint32_t num_fixels = Sparse::get_number_of_fixels (index_header); const float angular_threshold = get_option_value ("angle", DEFAULT_ANGLE_THRESHOLD); @@ -137,10 +137,10 @@ void run () std::vector directions (num_fixels); const std::string output_fixel_folder = argument[2]; - FixelFormat::copy_index_and_directions_file (input_fixel_folder, output_fixel_folder); + Sparse::copy_index_and_directions_file (input_fixel_folder, output_fixel_folder); { - auto directions_data = FixelFormat::find_directions_header (input_fixel_folder).get_image().with_direct_io(); + auto directions_data = Sparse::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) { @@ -148,7 +148,7 @@ void run () index_image.index(3) = 1; uint32_t offset = index_image.value(); size_t fixel_index = 0; - for (auto f = FixelFormat::FixelLoop (index_image) (directions_data); f; ++f, ++fixel_index) { + for (auto f = Sparse::FixelLoop (index_image) (directions_data); f; ++f, ++fixel_index) { directions[offset + fixel_index] = directions_data.row(1); positions[offset + fixel_index] = image_transform.voxel2scanner * vox; } @@ -180,7 +180,7 @@ void run () } track_file.close(); - Header output_header (FixelFormat::data_header_from_index (index_image)); + Header output_header (Sparse::data_header_from_index (index_image)); write_fixel_output (Path::join (output_fixel_folder, argument[3]), fixel_TDI, output_header); diff --git a/cmd/voxel2fixel.cpp b/cmd/voxel2fixel.cpp index bac3f403f1..126db10ce8 100644 --- a/cmd/voxel2fixel.cpp +++ b/cmd/voxel2fixel.cpp @@ -19,9 +19,9 @@ #include "algo/loop.h" #include "image.h" -#include "fixel_format/helpers.h" -#include "fixel_format/keys.h" -#include "fixel_format/loop.h" +#include "sparse/helpers.h" +#include "sparse/keys.h" +#include "sparse/loop.h" using namespace MR; using namespace App; @@ -47,22 +47,22 @@ void run () { auto scalar = Image::open (argument[0]); std::string input_fixel_directory = argument[1]; - FixelFormat::check_fixel_directory (input_fixel_directory); - auto input_fixel_index = FixelFormat::find_index_header (input_fixel_directory).get_image(); + Sparse::check_fixel_directory (input_fixel_directory); + auto input_fixel_index = Sparse::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"); progress++; - FixelFormat::copy_index_and_directions_file (input_fixel_directory, output_fixel_directory); + Sparse::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]), FixelFormat::data_header_from_index (input_fixel_index)); + auto output_fixel_data = Image::create (Path::join(output_fixel_directory, argument[3]), Sparse::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 = FixelFormat::FixelLoop (input_fixel_index) (output_fixel_data); f; ++f) + for (auto f = Sparse::FixelLoop (input_fixel_index) (output_fixel_data); f; ++f) output_fixel_data.value() = scalar.value(); } } diff --git a/cmd/warp2metric.cpp b/cmd/warp2metric.cpp index ee554d09dd..fc01919345 100644 --- a/cmd/warp2metric.cpp +++ b/cmd/warp2metric.cpp @@ -20,9 +20,9 @@ #include "image.h" #include "adapter/jacobian.h" #include "registration/warp/helpers.h" -#include "fixel_format/helpers.h" -#include "fixel_format/keys.h" -#include "fixel_format/loop.h" +#include "sparse/helpers.h" +#include "sparse/keys.h" +#include "sparse/loop.h" using namespace MR; using namespace App; @@ -79,13 +79,13 @@ void run () auto opt = get_options ("fc"); if (opt.size()) { std::string template_fixel_directory (opt[0][0]); - fixel_template_index = FixelFormat::find_index_header (template_fixel_directory).get_image(); - fixel_template_directions = FixelFormat::find_directions_header (template_fixel_directory).get_image().with_direct_io(); + fixel_template_index = Sparse::find_index_header (template_fixel_directory).get_image(); + fixel_template_directions = Sparse::find_directions_header (template_fixel_directory).get_image().with_direct_io(); std::string output_fixel_directory (opt[0][1]); if (template_fixel_directory != output_fixel_directory) { - FixelFormat::copy_index_file (template_fixel_directory, output_fixel_directory); - FixelFormat::copy_directions_file (template_fixel_directory, output_fixel_directory); + Sparse::copy_index_file (template_fixel_directory, output_fixel_directory); + Sparse::copy_directions_file (template_fixel_directory, output_fixel_directory); } uint32_t num_fixels = 0; @@ -93,7 +93,7 @@ void run () for (auto l = Loop (fixel_template_index, 0, 3) (fixel_template_index); l; ++l) num_fixels += fixel_template_index.value(); - fc_output_data = Image::create (Path::join (output_fixel_directory, opt[0][2]), FixelFormat::data_header_from_index (fixel_template_index)); + fc_output_data = Image::create (Path::join (output_fixel_directory, opt[0][2]), Sparse::data_header_from_index (fixel_template_index)); } @@ -121,7 +121,7 @@ void run () if (fc_output_data.valid()) { assign_pos_of (jacobian, 0, 3).to (fixel_template_index); - for (auto f = FixelFormat::FixelLoop (fixel_template_index) (fixel_template_directions, fc_output_data); f; ++f) { + for (auto f = Sparse::FixelLoop (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; diff --git a/lib/formats/mrtrix_sparse.cpp b/lib/formats/mrtrix_sparse_legacy.cpp similarity index 85% rename from lib/formats/mrtrix_sparse.cpp rename to lib/formats/mrtrix_sparse_legacy.cpp index 7c7df71819..aa41e502c0 100644 --- a/lib/formats/mrtrix_sparse.cpp +++ b/lib/formats/mrtrix_sparse_legacy.cpp @@ -26,7 +26,7 @@ #include "image_io/sparse.h" #include "formats/list.h" #include "formats/mrtrix_utils.h" -#include "sparse/keys.h" +#include "sparse/legacy/keys.h" @@ -62,11 +62,11 @@ namespace MR if (H.datatype() != dt) throw Exception ("Cannot open sparse image file " + H.name() + " due to type mismatch; expect " + dt.description() + ", file is " + H.datatype().description()); - const auto name_it = H.keyval().find (Sparse::name_key); + const auto name_it = H.keyval().find (Sparse::Legacy::name_key); if (name_it == H.keyval().end()) throw Exception ("sparse data class name not specified in sparse image header " + H.name()); - const auto size_it = H.keyval().find (Sparse::size_key); + const auto size_it = H.keyval().find (Sparse::Legacy::size_key); if (size_it == H.keyval().end()) throw Exception ("sparse data class size not specified in sparse image header " + H.name()); @@ -80,7 +80,7 @@ namespace MR get_mrtrix_file_path (H, "sparse_file", sparse_fname, sparse_offset); - std::unique_ptr io_handler (new ImageIO::Sparse (H, name_it->second, to(size_it->second), File::Entry (sparse_fname, sparse_offset))); + std::unique_ptr io_handler (new ImageIO::SparseLegacy (H, name_it->second, to(size_it->second), File::Entry (sparse_fname, sparse_offset))); for (size_t n = 0; n < image_list.size(); ++n) io_handler->files.push_back (File::Entry (image_list[n].name(), image_offset)); @@ -97,8 +97,8 @@ namespace MR !Path::has_suffix (H.name(), ".msf")) return false; - if (H.keyval().find (Sparse::name_key) == H.keyval().end() || - H.keyval().find (Sparse::size_key) == H.keyval().end()) + if (H.keyval().find (Sparse::Legacy::name_key) == H.keyval().end() || + H.keyval().find (Sparse::Legacy::size_key) == H.keyval().end()) return false; H.ndim() = num_axes; @@ -117,11 +117,11 @@ namespace MR std::unique_ptr MRtrix_sparse::create (Header& H) const { - const auto name_it = H.keyval().find (Sparse::name_key); + const auto name_it = H.keyval().find (Sparse::Legacy::name_key); if (name_it == H.keyval().end()) throw Exception ("Cannot create sparse image " + H.name() + "; no knowledge of underlying data class type"); - const auto size_it = H.keyval().find (Sparse::size_key); + const auto size_it = H.keyval().find (Sparse::Legacy::size_key); if (size_it == H.keyval().end()) throw Exception ("Cannot create sparse image " + H.name() + "; no knowledge of underlying data class size"); @@ -162,7 +162,7 @@ namespace MR } - std::unique_ptr io_handler (new ImageIO::Sparse (H, name_it->second, to(size_it->second), File::Entry (sparse_path, sparse_offset))); + std::unique_ptr io_handler (new ImageIO::SparseLegacy (H, name_it->second, to(size_it->second), File::Entry (sparse_path, sparse_offset))); io_handler->files.push_back (File::Entry (image_path, image_offset)); return std::move (io_handler); diff --git a/lib/image_io/sparse.cpp b/lib/image_io/sparse.cpp index 6461999f64..066c60de43 100644 --- a/lib/image_io/sparse.cpp +++ b/lib/image_io/sparse.cpp @@ -26,7 +26,7 @@ namespace MR - void Sparse::load (const Header& header, size_t) + void SparseLegacy::load (const Header& header, size_t) { Default::load (header,0); @@ -77,7 +77,7 @@ namespace MR - void Sparse::unload (const Header& header) + void SparseLegacy::unload (const Header& header) { Default::unload (header); @@ -103,7 +103,7 @@ namespace MR - uint64_t Sparse::set_numel (const uint64_t old_offset, const uint32_t numel) + uint64_t SparseLegacy::set_numel (const uint64_t old_offset, const uint32_t numel) { assert (is_image_readwrite()); diff --git a/lib/image_io/sparse.h b/lib/image_io/sparse.h index edd5e54947..0e6eb5ebeb 100644 --- a/lib/image_io/sparse.h +++ b/lib/image_io/sparse.h @@ -13,8 +13,8 @@ * */ -#ifndef __image_io_sparse_h__ -#define __image_io_sparse_h__ +#ifndef __image_io_sparse_legacy_h__ +#define __image_io_sparse_legacy_h__ #include #include @@ -63,11 +63,11 @@ namespace MR - class Sparse : public Default + class SparseLegacy : public Default { public: - Sparse (const Header& header, const std::string& sparse_class_name, const size_t sparse_class_size, const File::Entry& entry) : + SparseLegacy (const Header& header, const std::string& sparse_class_name, const size_t sparse_class_size, const File::Entry& entry) : Default (header), class_name (sparse_class_name), class_size (sparse_class_size), diff --git a/lib/sparse/fixel_metric.h b/lib/sparse/fixel_metric.h deleted file mode 100644 index bfe8d0148e..0000000000 --- a/lib/sparse/fixel_metric.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - -#ifndef __sparse_fixel_metric_h__ -#define __sparse_fixel_metric_h__ - -#include "types.h" - -namespace MR -{ - namespace Sparse - { - - - - // A class for storing a single quantitative value per fixel - // This simple class will form the basis of most fixel-based image outputs and statistical analysis - // Members are: - // * 'dir': orientation of fixel on unit vector xyz triplet - // * 'size': parameter related to the size of the fixel (e.g. FOD lobe integral, bolume fraction, FOD peak amplitude) - // * 'value': the parameteric value of interest associated with the fixel - class FixelMetric - { - public: - FixelMetric (const Eigen::Vector3f& d, const float s, const float v) : - dir (d), - size (s), - value (v) { } - FixelMetric () : - dir (), - size (0.0), - value (0.0) { } - Eigen::Vector3f dir; - float size; - float value; - }; - - - - } -} - -#endif - - - - diff --git a/lib/fixel_format/helpers.h b/lib/sparse/helpers.h similarity index 92% rename from lib/fixel_format/helpers.h rename to lib/sparse/helpers.h index f3b9c39a26..81f75563ab 100644 --- a/lib/fixel_format/helpers.h +++ b/lib/sparse/helpers.h @@ -13,11 +13,11 @@ * */ -#ifndef __fixel_helpers_h__ -#define __fixel_helpers_h__ +#ifndef __sparse_helpers_h__ +#define __sparse_helpers_h__ #include "formats/mrtrix_utils.h" -#include "fixel_format/keys.h" +#include "sparse/keys.h" #include "algo/loop.h" #include "image_diff.h" @@ -31,15 +31,15 @@ namespace MR : Exception(previous_exception, msg) {} }; - namespace FixelFormat + namespace Sparse { FORCE_INLINE bool is_index_image (const Header& in) { bool is_index = false; if (in.ndim() == 4) { if (in.size(3) == 2) { - for (std::initializer_list::iterator it = FixelFormat::supported_fixel_formats.begin(); - it != FixelFormat::supported_fixel_formats.end(); ++it) { + for (std::initializer_list::iterator it = supported_sparse_formats.begin(); + it != supported_sparse_formats.end(); ++it) { if (Path::basename (in.name()) == "index" + *it) is_index = true; } @@ -67,8 +67,8 @@ namespace MR bool is_directions = false; if (in.ndim() == 3) { if (in.size(1) == 3 && in.size(2) == 1) { - for (std::initializer_list::iterator it = FixelFormat::supported_fixel_formats.begin(); - it != FixelFormat::supported_fixel_formats.end(); ++it) { + for (std::initializer_list::iterator it = supported_sparse_formats.begin(); + it != supported_sparse_formats.end(); ++it) { if (Path::basename (in.name()) == "directions" + *it) is_directions = true; } @@ -181,8 +181,8 @@ namespace MR Header header; check_fixel_directory (fixel_directory_path); - for (std::initializer_list::iterator it = FixelFormat::supported_fixel_formats.begin(); - it != FixelFormat::supported_fixel_formats.end(); ++it) { + for (std::initializer_list::iterator it = supported_sparse_formats.begin(); + it !=supported_sparse_formats.end(); ++it) { std::string full_path = Path::join (fixel_directory_path, "index" + *it); if (Path::exists(full_path)) { if (header.valid()) @@ -212,7 +212,7 @@ namespace MR std::vector
data_headers; for (auto fname : file_names) { - if (Path::has_suffix (fname, FixelFormat::supported_fixel_formats)) { + if (Path::has_suffix (fname, supported_sparse_formats)) { try { auto H = Header::open (Path::join (fixel_directory_path, fname)); if (is_data_file (H)) { @@ -239,14 +239,14 @@ namespace MR bool directions_found (false); Header header; check_fixel_directory (fixel_directory_path); - Header index_header = FixelFormat::find_index_header (fixel_directory_path); + Header index_header = Sparse::find_index_header (fixel_directory_path); auto dir_walker = Path::Dir (fixel_directory_path); std::string fname; while ((fname = dir_walker.read_name ()).size ()) { Header tmp_header; auto full_path = Path::join (fixel_directory_path, fname); - if (Path::has_suffix (fname, FixelFormat::supported_fixel_formats) + if (Path::has_suffix (fname, supported_sparse_formats) && is_directions_file (tmp_header = Header::open (full_path))) { if (is_directions_file (tmp_header)) { if (fixels_match (index_header, tmp_header)) { @@ -300,7 +300,7 @@ namespace MR //! Copy the index file from one fixel directory into another FORCE_INLINE void copy_index_file (const std::string& input_directory, const std::string& output_directory) { - Header input_header = FixelFormat::find_index_header (input_directory); + Header input_header = Sparse::find_index_header (input_directory); check_fixel_directory (output_directory, true); std::string output_path = Path::join (output_directory, Path::basename (input_header.name())); @@ -323,7 +323,7 @@ namespace MR //! Copy the directions file from one fixel directory into another. FORCE_INLINE void copy_directions_file (const std::string& input_directory, const std::string& output_directory) { - Header input_header = FixelFormat::find_directions_header (input_directory); + Header input_header = Sparse::find_directions_header (input_directory); std::string output_path = Path::join (output_directory, Path::basename (input_header.name())); // If the index file already exists check it is the same as the input index file @@ -349,7 +349,7 @@ namespace MR //! Copy all data files in a fixel directory into another directory. Data files do not include the index or directions file. FORCE_INLINE void copy_all_data_files (const std::string &input_directory, const std::string &output_directory) { - for (auto& input_header : FixelFormat::find_data_headers (input_directory, FixelFormat::find_index_header (input_directory))) + for (auto& input_header : Sparse::find_data_headers (input_directory, Sparse::find_index_header (input_directory))) copy_fixel_file (input_header.name(), output_directory); } @@ -360,10 +360,10 @@ namespace MR throw Exception ("please input the specific fixel data file to be converted (not the fixel directory)"); Header in_data_header = Header::open (input_file); - FixelFormat::check_data_file (in_data_header); + Sparse::check_data_file (in_data_header); auto in_data_image = in_data_header.get_image(); - Header in_index_header = FixelFormat::find_index_header (FixelFormat::get_fixel_directory (input_file)); + Header in_index_header = Sparse::find_index_header (Sparse::get_fixel_directory (input_file)); if (input_file == in_index_header.name()) throw Exception ("input fixel data file cannot be the index file"); diff --git a/lib/sparse/image.h b/lib/sparse/image.h deleted file mode 100644 index 3217be0e3f..0000000000 --- a/lib/sparse/image.h +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - -#ifndef __sparse_image_h__ -#define __sparse_image_h__ - -#include - -#include "image.h" -#include "header.h" -#include "image_io/sparse.h" -#include "sparse/keys.h" - -#ifndef __image_h__ -#error File that #includes "sparse/image.h" must explicitly #include "image.h" beforehand -#endif - - -namespace MR -{ - namespace Sparse - { - - template - class Value { - public: - Value (::MR::Image& offsets, ImageIO::Sparse& io) : offsets (offsets), io (io) { } - - uint32_t size() const { return io.get_numel (offsets.value()); } - - void set_size (const uint32_t n) - { - // Handler allocates new memory if necessary, and sets the relevant number of elements flag in the sparse image data - // It returns the file offset necessary to access the relevant memory, so update the raw image value accordingly - offsets.value() = (io.set_numel (offsets.value(), n)); - } - - // Handler is responsible for bounds checking - DataType& operator[] (const size_t i) - { - uint8_t* const ptr = io.get (offsets.value(), i); - return *(reinterpret_cast(ptr)); - } - const DataType& operator[] (const size_t i) const - { - const uint8_t* const ptr = io.get (offsets.value(), i); - return *(reinterpret_cast(ptr)); - } - - - // This should provide image copying capability using the relevant templated functions - Value& operator= (const Value& that) { - set_size (that.size()); - for (uint32_t i = 0; i != size(); ++i) - (*this)[i] = that[i]; - return *this; - } - - - friend std::ostream& operator<< (std::ostream& stream, const Value& value) { - stream << "Position [ "; - for (size_t n = 0; n < value.offsets.ndim(); ++n) - stream << value.offsets[n] << " "; - stream << "], offset = " << value.offsets.value() << ", " << value.size() << " elements"; - return stream; - } - - - - protected: - ::MR::Image& offsets; - ImageIO::Sparse& io; - }; - - - - - - - - // A convenience class for wrapping access to sparse images - template - class Image : public ::MR::Image - { - public: - Image (const std::string& image_name) : - ::MR::Image (::MR::Image::open (image_name)), io (nullptr) { check(); } - - Image (Header& header) : - ::MR::Image (header.get_image()), io (nullptr) { check(); } - - Image (const Image& that) = default; - - Image (const std::string& image_name, const Header& template_header) : - ::MR::Image (::MR::Image::create (image_name, template_header)), io (nullptr) { check(); } - - typedef uint64_t value_type; - typedef DataType sparse_data_type; - - Value value () { return { *this, *io }; } - const Value value () const { return { *this, *io }; } - - protected: - ImageIO::Sparse* io; - - void check() - { - if (!buffer || !buffer->get_io()) - throw Exception ("cannot create sparse image for image with no handler"); - ImageIO::Base* ptr = buffer->get_io(); - if (typeid (*ptr) != typeid (ImageIO::Sparse)) - throw Exception ("cannot create sparse image to access non-sparse data"); - // Use the header information rather than trying to access this from the handler - std::map::const_iterator name_it = keyval().find (Sparse::name_key); - if (name_it == keyval().end()) - throw Exception ("cannot create sparse image without knowledge of underlying class type in the image header"); - // TODO temporarily disabled this to allow updated_syntax tests to pass with files generated with master branch. -// const std::string& class_name = name_it->second; -// if (str(typeid(DataType).name()) != class_name) -// throw Exception ("class type of sparse image buffer (" + str(typeid(DataType).name()) + ") does not match that in image header (" + class_name + ")"); - std::map::const_iterator size_it = keyval().find (Sparse::size_key); - if (size_it == keyval().end()) - throw Exception ("cannot create sparse image without knowledge of underlying class size in the image header"); - const size_t class_size = to(size_it->second); - if (sizeof(DataType) != class_size) - throw Exception ("class size of sparse image does not match that in image header"); - io = reinterpret_cast (buffer->get_io()); - DEBUG ("Sparse image verified for accessing " + name() + " using type " + str(typeid(DataType).name())); - } - - - }; - - - - } -} - -#endif - - - diff --git a/lib/sparse/keys.h b/lib/sparse/keys.h index b18916a8c7..eb168769bc 100644 --- a/lib/sparse/keys.h +++ b/lib/sparse/keys.h @@ -1,16 +1,16 @@ /* * Copyright (c) 2008-2016 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/ - * + * * MRtrix is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * + * * For more details, see www.mrtrix.org - * + * */ #ifndef __sparse_keys_h__ @@ -22,18 +22,9 @@ namespace MR { namespace Sparse { - - - // These are the keys that must be present in an image header to successfully read or write sparse image data - const std::string name_key ("sparse_data_name"); - const std::string size_key ("sparse_data_size"); - - + const std::string n_fixels_key ("nfixels"); + const std::initializer_list supported_sparse_formats { ".mif", ".nii", ".mif.gz" , ".nii.gz" }; } } #endif - - - - diff --git a/lib/sparse/legacy/fixel_metric.h b/lib/sparse/legacy/fixel_metric.h new file mode 100644 index 0000000000..1777550b41 --- /dev/null +++ b/lib/sparse/legacy/fixel_metric.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __sparse_legacy_metric_h__ +#define __sparse_legacy_metric_h__ + +#include "types.h" + +namespace MR +{ + namespace Sparse + { + namespace Legacy + { + + + // A class for storing a single quantitative value per fixel + // This simple class will form the basis of most fixel-based image outputs and statistical analysis + // Members are: + // * 'dir': orientation of fixel on unit vector xyz triplet + // * 'size': parameter related to the size of the fixel (e.g. FOD lobe integral, bolume fraction, FOD peak amplitude) + // * 'value': the parameteric value of interest associated with the fixel + class FixelMetric + { + public: + FixelMetric (const Eigen::Vector3f& d, const float s, const float v) : + dir (d), + size (s), + value (v) { } + FixelMetric () : + dir (), + size (0.0), + value (0.0) { } + Eigen::Vector3f dir; + float size; + float value; + }; + + + } + } +} + +#endif + + + + diff --git a/lib/sparse/legacy/image.h b/lib/sparse/legacy/image.h new file mode 100644 index 0000000000..aac230b77b --- /dev/null +++ b/lib/sparse/legacy/image.h @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __sparse_legacy_image_h__ +#define __sparse_legacy_image_h__ + +#include + +#include "image.h" +#include "header.h" +#include "image_io/sparse.h" +#include "sparse/legacy/keys.h" + +#ifndef __image_h__ +#error File that #includes "sparse/legacy/image.h" must explicitly #include "image.h" beforehand +#endif + + +namespace MR +{ + namespace Sparse + { + + namespace Legacy + { + + template + class Value { + public: + Value (::MR::Image& offsets, ImageIO::SparseLegacy& io) : offsets (offsets), io (io) { } + + uint32_t size() const { return io.get_numel (offsets.value()); } + + void set_size (const uint32_t n) + { + // Handler allocates new memory if necessary, and sets the relevant number of elements flag in the sparse image data + // It returns the file offset necessary to access the relevant memory, so update the raw image value accordingly + offsets.value() = (io.set_numel (offsets.value(), n)); + } + + // Handler is responsible for bounds checking + DataType& operator[] (const size_t i) + { + uint8_t* const ptr = io.get (offsets.value(), i); + return *(reinterpret_cast(ptr)); + } + const DataType& operator[] (const size_t i) const + { + const uint8_t* const ptr = io.get (offsets.value(), i); + return *(reinterpret_cast(ptr)); + } + + + // This should provide image copying capability using the relevant templated functions + Value& operator= (const Value& that) { + set_size (that.size()); + for (uint32_t i = 0; i != size(); ++i) + (*this)[i] = that[i]; + return *this; + } + + + friend std::ostream& operator<< (std::ostream& stream, const Value& value) { + stream << "Position [ "; + for (size_t n = 0; n < value.offsets.ndim(); ++n) + stream << value.offsets[n] << " "; + stream << "], offset = " << value.offsets.value() << ", " << value.size() << " elements"; + return stream; + } + + + + protected: + ::MR::Image& offsets; + ImageIO::SparseLegacy& io; + }; + + + + + + + + // A convenience class for wrapping access to sparse images + template + class Image : public ::MR::Image + { + public: + Image (const std::string& image_name) : + ::MR::Image (::MR::Image::open (image_name)), io (nullptr) { check(); } + + Image (Header& header) : + ::MR::Image (header.get_image()), io (nullptr) { check(); } + + Image (const Image& that) = default; + + Image (const std::string& image_name, const Header& template_header) : + ::MR::Image (::MR::Image::create (image_name, template_header)), io (nullptr) { check(); } + + typedef uint64_t value_type; + typedef DataType sparse_data_type; + + Value value () { return { *this, *io }; } + const Value value () const { return { *this, *io }; } + + protected: + ImageIO::SparseLegacy* io; + + void check() + { + if (!buffer || !buffer->get_io()) + throw Exception ("cannot create sparse image for image with no handler"); + ImageIO::Base* ptr = buffer->get_io(); + if (typeid (*ptr) != typeid (ImageIO::SparseLegacy)) + throw Exception ("cannot create sparse image to access non-sparse data"); + // Use the header information rather than trying to access this from the handler + std::map::const_iterator name_it = keyval().find (Sparse::Legacy::name_key); + if (name_it == keyval().end()) + throw Exception ("cannot create sparse image without knowledge of underlying class type in the image header"); + // TODO temporarily disabled this to allow updated_syntax tests to pass with files generated with master branch. + // const std::string& class_name = name_it->second; + // if (str(typeid(DataType).name()) != class_name) + // throw Exception ("class type of sparse image buffer (" + str(typeid(DataType).name()) + ") does not match that in image header (" + class_name + ")"); + std::map::const_iterator size_it = keyval().find (Sparse::Legacy::size_key); + if (size_it == keyval().end()) + throw Exception ("cannot create sparse image without knowledge of underlying class size in the image header"); + const size_t class_size = to(size_it->second); + if (sizeof(DataType) != class_size) + throw Exception ("class size of sparse image does not match that in image header"); + io = reinterpret_cast (buffer->get_io()); + DEBUG ("Sparse image verified for accessing " + name() + " using type " + str(typeid(DataType).name())); + } + + + }; + + + } + } +} + +#endif + + + diff --git a/lib/fixel_format/keys.h b/lib/sparse/legacy/keys.h similarity index 59% rename from lib/fixel_format/keys.h rename to lib/sparse/legacy/keys.h index 9f992a9007..7b55334f0a 100644 --- a/lib/fixel_format/keys.h +++ b/lib/sparse/legacy/keys.h @@ -1,30 +1,39 @@ /* * Copyright (c) 2008-2016 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/ - * + * * MRtrix is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * + * * For more details, see www.mrtrix.org - * + * */ -#ifndef __fixel_keys_h__ -#define __fixel_keys_h__ +#ifndef __sparse_legacy_keys_h__ +#define __sparse_legacy_keys_h__ #include namespace MR { - namespace FixelFormat + namespace Sparse { - const std::string n_fixels_key ("nfixels"); - const std::initializer_list supported_fixel_formats { ".mif", ".nii", ".mif.gz" , ".nii.gz" }; + + namespace Legacy + { + // These are the keys that must be present in an image header to successfully read or write sparse image data + const std::string name_key ("sparse_data_name"); + const std::string size_key ("sparse_data_size"); + } } } #endif + + + + diff --git a/lib/fixel_format/loop.h b/lib/sparse/loop.h similarity index 96% rename from lib/fixel_format/loop.h rename to lib/sparse/loop.h index a3476599ea..f84b5bd31f 100644 --- a/lib/fixel_format/loop.h +++ b/lib/sparse/loop.h @@ -13,14 +13,14 @@ * */ -#ifndef __fixelformat_loop_h__ -#define __fixelformat_loop_h__ +#ifndef __sparse_loop_h__ +#define __sparse_loop_h__ #include "formats/mrtrix_utils.h" namespace MR { - namespace FixelFormat { + namespace Sparse { namespace { diff --git a/src/dwi/tractography/SIFT/output.h b/src/dwi/tractography/SIFT/output.h index be145c6ffd..ce8da7b9e3 100644 --- a/src/dwi/tractography/SIFT/output.h +++ b/src/dwi/tractography/SIFT/output.h @@ -31,9 +31,9 @@ #include "math/SH.h" -#include "sparse/fixel_metric.h" -#include "sparse/image.h" -#include "sparse/keys.h" +#include "sparse/legacy/fixel_metric.h" +#include "sparse/legacy/image.h" +#include "sparse/legacy/keys.h" namespace MR @@ -98,13 +98,13 @@ namespace MR template void ModelBase::output_target_image_fixel (const std::string& path) const { - using Sparse::FixelMetric; + using Sparse::Legacy::FixelMetric; Header H_fixel (Fixel_map::header()); H_fixel.datatype() = DataType::UInt64; H_fixel.datatype().set_byte_order_native(); - H_fixel.keyval()[Sparse::name_key] = str(typeid(FixelMetric).name()); - H_fixel.keyval()[Sparse::size_key] = str(sizeof(FixelMetric)); - Sparse::Image out (path, H_fixel); + H_fixel.keyval()[Sparse::Legacy::name_key] = str(typeid(FixelMetric).name()); + H_fixel.keyval()[Sparse::Legacy::size_key] = str(sizeof(FixelMetric)); + Sparse::Legacy::Image out (path, H_fixel); VoxelAccessor v (accessor()); for (auto l = Loop (out) (out, v); l; ++l) { if (v.value()) { @@ -192,14 +192,14 @@ namespace MR template void ModelBase::output_tdi_fixel (const std::string& path) const { - using Sparse::FixelMetric; + using Sparse::Legacy::FixelMetric; const default_type current_mu = mu(); Header H_fixel (Fixel_map::header()); H_fixel.datatype() = DataType::UInt64; H_fixel.datatype().set_byte_order_native(); - H_fixel.keyval()[Sparse::name_key] = str(typeid(FixelMetric).name()); - H_fixel.keyval()[Sparse::size_key] = str(sizeof(FixelMetric)); - Sparse::Image out (path, H_fixel); + H_fixel.keyval()[Sparse::Legacy::name_key] = str(typeid(FixelMetric).name()); + H_fixel.keyval()[Sparse::Legacy::size_key] = str(sizeof(FixelMetric)); + Sparse::Legacy::Image out (path, H_fixel); VoxelAccessor v (accessor()); for (auto l = Loop (out) (out, v); l; ++l) { if (v.value()) { @@ -244,15 +244,15 @@ namespace MR template void ModelBase::output_error_fixel_images (const std::string& diff_path, const std::string& cost_path) const { - using Sparse::FixelMetric; + using Sparse::Legacy::FixelMetric; const default_type current_mu = mu(); Header H_fixel (Fixel_map::header()); H_fixel.datatype() = DataType::UInt64; H_fixel.datatype().set_byte_order_native(); - H_fixel.keyval()[Sparse::name_key] = str(typeid(FixelMetric).name()); - H_fixel.keyval()[Sparse::size_key] = str(sizeof(FixelMetric)); - Sparse::Image out_diff (diff_path, H_fixel); - Sparse::Image out_cost (cost_path, H_fixel); + H_fixel.keyval()[Sparse::Legacy::name_key] = str(typeid(FixelMetric).name()); + H_fixel.keyval()[Sparse::Legacy::size_key] = str(sizeof(FixelMetric)); + Sparse::Legacy::Image out_diff (diff_path, H_fixel); + Sparse::Legacy::Image out_cost (cost_path, H_fixel); VoxelAccessor v (accessor()); for (auto l = Loop (v) (v, out_diff, out_cost); l; ++l) { if (v.value()) { diff --git a/src/dwi/tractography/SIFT/sifter.cpp b/src/dwi/tractography/SIFT/sifter.cpp index 635a9727e5..acf7ffdda3 100644 --- a/src/dwi/tractography/SIFT/sifter.cpp +++ b/src/dwi/tractography/SIFT/sifter.cpp @@ -34,7 +34,7 @@ #include "math/rng.h" -#include "sparse/image.h" +#include "sparse/legacy/image.h" diff --git a/src/dwi/tractography/SIFT2/tckfactor.cpp b/src/dwi/tractography/SIFT2/tckfactor.cpp index 628482675c..3f8a944a6d 100644 --- a/src/dwi/tractography/SIFT2/tckfactor.cpp +++ b/src/dwi/tractography/SIFT2/tckfactor.cpp @@ -20,9 +20,9 @@ #include "math/math.h" -#include "sparse/fixel_metric.h" -#include "sparse/image.h" -#include "sparse/keys.h" +#include "sparse/legacy/fixel_metric.h" +#include "sparse/legacy/image.h" +#include "sparse/legacy/keys.h" #include "dwi/tractography/SIFT2/coeff_optimiser.h" #include "dwi/tractography/SIFT2/fixel_updater.h" @@ -384,20 +384,20 @@ namespace MR { maxs[i] = 0.0; } - using Sparse::FixelMetric; + using Sparse::Legacy::FixelMetric; Header H_fixel (Fixel_map::header()); H_fixel.datatype() = DataType::UInt64; H_fixel.datatype().set_byte_order_native(); - H_fixel.keyval()[Sparse::name_key] = str(typeid(FixelMetric).name()); - H_fixel.keyval()[Sparse::size_key] = str(sizeof(FixelMetric)); - - Sparse::Image count_image (prefix + "_count.msf", H_fixel); - Sparse::Image min_image (prefix + "_coeff_min.msf", H_fixel); - Sparse::Image mean_image (prefix + "_coeff_mean.msf", H_fixel); - Sparse::Image stdev_image (prefix + "_coeff_stdev.msf", H_fixel); - Sparse::Image max_image (prefix + "_coeff_max.msf", H_fixel); - Sparse::Image zeroed_image (prefix + "_coeff_zeroed.msf", H_fixel); - Sparse::Image excluded_image (prefix + "_excluded.msf", H_fixel); + H_fixel.keyval()[Sparse::Legacy::name_key] = str(typeid(FixelMetric).name()); + H_fixel.keyval()[Sparse::Legacy::size_key] = str(sizeof(FixelMetric)); + + Sparse::Legacy::Image count_image (prefix + "_count.msf", H_fixel); + Sparse::Legacy::Image min_image (prefix + "_coeff_min.msf", H_fixel); + Sparse::Legacy::Image mean_image (prefix + "_coeff_mean.msf", H_fixel); + Sparse::Legacy::Image stdev_image (prefix + "_coeff_stdev.msf", H_fixel); + Sparse::Legacy::Image max_image (prefix + "_coeff_max.msf", H_fixel); + Sparse::Legacy::Image zeroed_image (prefix + "_coeff_zeroed.msf", H_fixel); + Sparse::Legacy::Image excluded_image (prefix + "_excluded.msf", H_fixel); VoxelAccessor v (accessor()); for (auto l = Loop(v) (v, count_image, min_image, mean_image, stdev_image, max_image, zeroed_image, excluded_image); l; ++l) { diff --git a/src/dwi/tractography/seeding/dynamic.cpp b/src/dwi/tractography/seeding/dynamic.cpp index 0ffb7cb3b9..2cacd150e2 100644 --- a/src/dwi/tractography/seeding/dynamic.cpp +++ b/src/dwi/tractography/seeding/dynamic.cpp @@ -21,9 +21,9 @@ #include "dwi/tractography/rng.h" #include "dwi/tractography/seeding/dynamic.h" -#include "sparse/fixel_metric.h" -#include "sparse/keys.h" -#include "sparse/image.h" +#include "sparse/legacy/fixel_metric.h" +#include "sparse/legacy/keys.h" +#include "sparse/legacy/image.h" @@ -136,9 +136,9 @@ namespace MR H.info() = info(); H.datatype() = DataType::UInt64; H.datatype().set_byte_order_native(); - H[Image::Sparse::name_key] = str(typeid(Image::Sparse::FixelMetric).name()); - H[Image::Sparse::size_key] = str(sizeof(Image::Sparse::FixelMetric)); - Image::BufferSparse buffer_probs ("final_seed_probs.msf", H), buffer_logprobs ("final_seed_logprobs.msf", H), buffer_ratios ("final_fixel_ratios.msf", H); + H[Image::Sparse::Legacy::name_key] = str(typeid(Image::Sparse::Legacy::FixelMetric).name()); + H[Image::Sparse::Legacy::size_key] = str(sizeof(Image::Sparse::Legacy::FixelMetric)); + Image::BufferSparse buffer_probs ("final_seed_probs.msf", H), buffer_logprobs ("final_seed_logprobs.msf", H), buffer_ratios ("final_fixel_ratios.msf", H); auto out_probs = buffer_probs.voxel(), out_logprobs = buffer_logprobs.voxel(), out_ratios = buffer_ratios.voxel(); VoxelAccessor v (accessor); Image::Loop loop; @@ -149,7 +149,7 @@ namespace MR out_ratios .value().set_size ((*v.value()).num_fixels()); size_t index = 0; for (Fixel_map::ConstIterator i = begin (v); i; ++i, ++index) { - Image::Sparse::FixelMetric fixel (i().get_dir(), i().get_FOD(), i().get_old_prob()); + Image::Sparse::Legacy::FixelMetric fixel (i().get_dir(), i().get_FOD(), i().get_old_prob()); out_probs.value()[index] = fixel; fixel.value = log10 (fixel.value); out_logprobs.value()[index] = fixel; @@ -286,9 +286,9 @@ namespace MR H.info() = info(); H.datatype() = DataType::UInt64; H.datatype().set_byte_order_native(); - H[Image::Sparse::name_key] = str(typeid(Image::Sparse::FixelMetric).name()); - H[Image::Sparse::size_key] = str(sizeof(Image::Sparse::FixelMetric)); - Image::BufferSparse buffer_probs ("mid_seed_probs.msf", H), buffer_logprobs ("mid_seed_logprobs.msf", H), buffer_ratios ("mid_fixel_ratios.msf", H), buffer_FDs ("mid_FDs.msf", H), buffer_TDs ("mid_TDs.msf", H); + H[Image::Sparse::Legacy::name_key] = str(typeid(Image::Sparse::Legacy::FixelMetric).name()); + H[Image::Sparse::Legacy::size_key] = str(sizeof(Image::Sparse::Legacy::FixelMetric)); + Image::BufferSparse buffer_probs ("mid_seed_probs.msf", H), buffer_logprobs ("mid_seed_logprobs.msf", H), buffer_ratios ("mid_fixel_ratios.msf", H), buffer_FDs ("mid_FDs.msf", H), buffer_TDs ("mid_TDs.msf", H); auto out_probs = buffer_probs.voxel(), out_logprobs = buffer_logprobs.voxel(), out_ratios = buffer_ratios.voxel(), out_FDs = buffer_FDs.voxel(), out_TDs = buffer_TDs.voxel(); VoxelAccessor v (accessor); Image::Loop loop; @@ -301,7 +301,7 @@ namespace MR out_TDs .value().set_size ((*v.value()).num_fixels()); size_t index = 0; for (Fixel_map::ConstIterator i = begin (v); i; ++i, ++index) { - Image::Sparse::FixelMetric fixel (i().get_dir(), i().get_FOD(), i().get_old_prob()); + Image::Sparse::Legacy::FixelMetric fixel (i().get_dir(), i().get_FOD(), i().get_old_prob()); out_probs.value()[index] = fixel; fixel.value = log10 (fixel.value); out_logprobs.value()[index] = fixel; @@ -341,9 +341,9 @@ namespace MR H.info() = info(); H.datatype() = DataType::UInt64; H.datatype().set_byte_order_native(); - H[Image::Sparse::name_key] = str(typeid(Image::Sparse::FixelMetric).name()); - H[Image::Sparse::size_key] = str(sizeof(Image::Sparse::FixelMetric)); - Image::BufferSparse buffer ("fixel_mask.msf", H); + H[Image::Sparse::Legacy::name_key] = str(typeid(Image::Sparse::Legacy::FixelMetric).name()); + H[Image::Sparse::Legacy::size_key] = str(sizeof(Image::Sparse::Legacy::FixelMetric)); + Image::BufferSparse buffer ("fixel_mask.msf", H); auto out = buffer.voxel(); VoxelAccessor v (accessor); Image::Loop loop; @@ -352,7 +352,7 @@ namespace MR out.value().set_size ((*v.value()).num_fixels()); size_t index = 0; for (Fixel_map::ConstIterator f = begin(v); f; ++f, ++index) { - Image::Sparse::FixelMetric fixel (f().get_dir(), f().get_FOD(), (f().can_update() ? 1.0f : 0.0f)); + Image::Sparse::Legacy::FixelMetric fixel (f().get_dir(), f().get_FOD(), (f().can_update() ? 1.0f : 0.0f)); out.value()[index] = fixel; } } diff --git a/src/gui/mrview/tool/vector/fixel.h b/src/gui/mrview/tool/vector/fixel.h index 2ff7344624..d41d933429 100644 --- a/src/gui/mrview/tool/vector/fixel.h +++ b/src/gui/mrview/tool/vector/fixel.h @@ -22,9 +22,9 @@ #include "transform.h" #include "algo/loop.h" -#include "sparse/image.h" -#include "sparse/fixel_metric.h" -#include "fixel_format/helpers.h" +#include "sparse/legacy/image.h" +#include "sparse/legacy/fixel_metric.h" +#include "sparse/helpers.h" #include "gui/mrview/displayable.h" #include "gui/mrview/tool/vector/vector.h" @@ -319,7 +319,7 @@ namespace MR } }; - typedef MR::Sparse::Image FixelSparseImageType; + typedef MR::Sparse::Legacy::Image FixelSparseImageType; typedef MR::Image FixelPackedImageType; typedef MR::Image FixelIndexImageType; } diff --git a/src/gui/mrview/tool/vector/fixelfolder.cpp b/src/gui/mrview/tool/vector/fixelfolder.cpp index 64a425dfe0..a63b3dbbe7 100644 --- a/src/gui/mrview/tool/vector/fixelfolder.cpp +++ b/src/gui/mrview/tool/vector/fixelfolder.cpp @@ -60,7 +60,7 @@ namespace MR // Load fixel direction images - auto directions_image = FixelFormat::find_directions_header (Path::dirname (fixel_data->name())).get_image().with_direct_io (); + auto directions_image = Sparse::find_directions_header (Path::dirname (fixel_data->name())).get_image().with_direct_io (); directions_image.index (1) = 0; for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { fixel_data->index (3) = 0; @@ -75,7 +75,7 @@ namespace MR // Load fixel data images keys // We will load the actual fixel data lazily upon request - auto data_headers = FixelFormat::find_data_headers (Path::dirname (fixel_data->name ()), *fixel_data); + auto data_headers = Sparse::find_data_headers (Path::dirname (fixel_data->name ()), *fixel_data); for (auto& header : data_headers) { if (header.size (1) != 1) continue; @@ -99,7 +99,7 @@ namespace MR auto H = Header::open (data_filepath); - if (!FixelFormat::is_data_file (H)) + if (!Sparse::is_data_file (H)) return; auto data_image = H.get_image (); diff --git a/src/gui/mrview/tool/vector/fixelfolder.h b/src/gui/mrview/tool/vector/fixelfolder.h index 9316be8adb..224389e475 100644 --- a/src/gui/mrview/tool/vector/fixelfolder.h +++ b/src/gui/mrview/tool/vector/fixelfolder.h @@ -29,7 +29,7 @@ namespace MR { public: FixelFolder (const std::string& filename, Vector& fixel_tool) : - FixelType (FixelFormat::find_index_header (Path::dirname (filename)).name (), fixel_tool) + FixelType (Sparse::find_index_header (Path::dirname (filename)).name (), fixel_tool) { value_types = {"unity"}; colour_types = {"direction"}; diff --git a/testing/cmd/testing_diff_fixel.cpp b/testing/cmd/testing_diff_fixel.cpp index c7d15ea8be..e25a37e548 100644 --- a/testing/cmd/testing_diff_fixel.cpp +++ b/testing/cmd/testing_diff_fixel.cpp @@ -18,7 +18,7 @@ #include "image.h" -#include "fixel_format/helpers.h" +#include "sparse/helpers.h" #include "diff_images.h" @@ -45,9 +45,9 @@ void usage () void run () { std::string fixel_directory1 = argument[0]; - FixelFormat::check_fixel_directory (fixel_directory1); + Sparse::check_fixel_directory (fixel_directory1); std::string fixel_directory2 = argument[1]; - FixelFormat::check_fixel_directory (fixel_directory2); + Sparse::check_fixel_directory (fixel_directory2); if (fixel_directory1 == fixel_directory2) throw Exception ("Input fixel directorys are the same"); diff --git a/testing/cmd/testing_diff_fixel_old.cpp b/testing/cmd/testing_diff_fixel_old.cpp index 4620e87660..6cc1c77534 100644 --- a/testing/cmd/testing_diff_fixel_old.cpp +++ b/testing/cmd/testing_diff_fixel_old.cpp @@ -25,7 +25,7 @@ #include "sparse/fixel_metric.h" #include "image_helpers.h" #include "algo/threaded_loop.h" -using MR::Sparse::FixelMetric; +using MR::Sparse::Legacy::FixelMetric; using namespace MR; using namespace App; @@ -46,8 +46,8 @@ void usage () void run () { - Sparse::Image buffer1 (argument[0]); - Sparse::Image buffer2 (argument[1]); + Sparse::Legacy::Image buffer1 (argument[0]); + Sparse::Legacy::Image buffer2 (argument[1]); check_dimensions (buffer1, buffer2); for (size_t i = 0; i < buffer1.ndim(); ++i) { if (std::isfinite (buffer1.spacing(i))) From 23c62a475ddfce3868aa74a77aadff10ac5027dc Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Wed, 16 Nov 2016 17:03:45 +0000 Subject: [PATCH 232/723] configure: remove -mno-avx flag --- configure | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 0e887b6353..b64c0d04f6 100755 --- a/configure +++ b/configure @@ -215,7 +215,7 @@ global cpp, cpp_cmd, ld, ld_args, ld_cmd cxx = [ 'g++' ] cxx_args = '-c CFLAGS SRC -o OBJECT'.split() -cpp_flags = [ '-std=c++11', '-mno-avx' ] +cpp_flags = [ '-std=c++11' ] ld_args = 'OBJECTS LDFLAGS -o EXECUTABLE'.split() ld_flags = [ ] @@ -775,7 +775,7 @@ int main (int argc, char* argv[]) ''', cpp_flags, ld_flags) - cpp_flags += [ '-DMRTRIX_ALLOC_MEMALIGN='+memalign ] + cpp_flags += [ '-DMRTRIX_ALLOC_MEM_ALIGN='+memalign ] report (memalign + ' byte\n') except CompileError: error (''' error compiling application! From 8fd2df6f51114778e1246bc6a383a8e5a2144641 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Wed, 16 Nov 2016 22:47:33 +0000 Subject: [PATCH 233/723] Eigen alignment: initial attempt at using static_assert --- cmd/sh2amp.cpp | 1 + lib/header.h | 3 +- lib/image.h | 11 +++-- lib/types.h | 125 ++++++++++++++++++++++++++++++------------------- 4 files changed, 86 insertions(+), 54 deletions(-) diff --git a/cmd/sh2amp.cpp b/cmd/sh2amp.cpp index abad1db155..0b69cbf7b6 100644 --- a/cmd/sh2amp.cpp +++ b/cmd/sh2amp.cpp @@ -86,6 +86,7 @@ void run () { auto sh_data = Image::open(argument[0]); Math::SH::check (sh_data); + sh_data.__check_memalign(); Header amp_header (sh_data); diff --git a/lib/header.h b/lib/header.h index 0c3b767fde..3e07d561e1 100644 --- a/lib/header.h +++ b/lib/header.h @@ -42,8 +42,7 @@ namespace MR template class Image; - class Header - { + class Header { MEM_ALIGN public: class Axis; diff --git a/lib/image.h b/lib/image.h index a94a434b5d..cab78e31f4 100644 --- a/lib/image.h +++ b/lib/image.h @@ -35,7 +35,7 @@ namespace MR template - class Image { + class Image { MEM_ALIGN public: typedef ValueType value_type; class Buffer; @@ -214,6 +214,7 @@ namespace MR size_t data_offset; }; + CHECK_MEM_ALIGN (Image); @@ -221,8 +222,7 @@ namespace MR template - class Image::Buffer : public Header - { + class Image::Buffer : public Header { NO_MEM_ALIGN public: //! construct a Buffer object to access the data in the image specified Buffer (Header& H, bool read_write_if_existing = false); @@ -257,6 +257,7 @@ namespace MR } }; + CHECK_MEM_ALIGN (Image::Buffer); @@ -276,7 +277,7 @@ namespace MR // lightweight struct to copy data into: template - struct TmpImage { + struct TmpImage { NO_MEM_ALIGN typedef ValueType value_type; const typename Image::Buffer& b; @@ -299,6 +300,8 @@ namespace MR FORCE_INLINE auto value () -> decltype (Helper::value (*this)) { return { *this }; } FORCE_INLINE void set_value (ValueType val) { Raw::store_native (val, data, offset); } }; + + CHECK_MEM_ALIGN (TmpImage); } diff --git a/lib/types.h b/lib/types.h index e54e97d169..52623cb8ed 100644 --- a/lib/types.h +++ b/lib/types.h @@ -109,57 +109,86 @@ #endif -/*! \def MEMALIGN(classname) - * to be placed immediately after \e every class or struct definition, to - * ensure the class had appropriate alignment-aware new & delete operators. - * This is required to ensure correct operation with Eigen >= 3.3 (due to the - * 32-byte alignment requirement for AVX instructions). +#ifndef EIGEN_DEFAULT_ALIGN_BYTES +// Assume 16 byte alignment as hard-coded in Eigen 3.2: +# define EIGEN_DEFAULT_ALIGN_BYTES 16 +#endif + +template class __has_custom_new_operator { + template static inline char test (decltype(C::operator new (sizeof(C)))) ; + template static inline long test (...); + public: + enum { value = sizeof(test(nullptr)) == sizeof(char) }; +}; + +inline void* __aligned_alloc (std::size_t size) { + auto* original = std::malloc (size + EIGEN_DEFAULT_ALIGN_BYTES); + if (!original) throw std::bad_alloc(); + void *aligned = reinterpret_cast((reinterpret_cast(original) & ~(std::size_t(EIGEN_DEFAULT_ALIGN_BYTES-1))) + EIGEN_DEFAULT_ALIGN_BYTES); + *(reinterpret_cast(aligned) - 1) = original; + return aligned; +} + +inline void __aligned_delete (void* ptr) { if (ptr) std::free (*(reinterpret_cast(ptr) - 1)); } + +/*! \def NO_MEM_ALIGN + * used to signal that the class does not have special alignment + * requirements, and so does not need a custom operator new method. + * + * The compiler will check whether this is indeed the case, and fail with an + * appropriate warning if this is not true. In this case, you need to replace + * NO_MEM_ALIGN with MEM_ALIGN. + * \sa MEM_ALIGN + * \sa CHECK_MEM_ALIGN + */ +#define NO_MEM_ALIGN \ + void __check_memalign () { static_assert (alignof(*this) <= MRTRIX_ALLOC_MEM_ALIGN, "please change to MEM_ALIGN for this class"); } + +/*! \def MEM_ALIGN + * used to signal that the class has special alignment requirements, and so + * needs a custom memory-aligned operator new method. * - * A big problem with this is that \e every class that could contain - * fixed-sized Eigen types (e.g. Matrix4d) must have these operators defined in - * case any instances get allocated dynamically. The default new operator - * provides no alignment guarantees, even for types declared using alignas(). - * It is very difficult to keep track of all such classes to make sure they are - * all appropriately defined. The approach here will only enforce memory - * alignment for those class that declare themselves are requirement wider - * alignment than that provided by the default new operator (typically 16 byte - * with gcc), using the default allocator otherwise. This means it can be used - * for \e all classes indiscriminately with no loss of performance or memory - * use efficiency, and under certain conditions makes it possible to largely - * automate the process of ensuring compliance, as long as all class or struct - * definitions all on the same line, for example: - * \code - * template class AlignMe { MEMALIGN(AlignMe) - * ... - * }; - * \endcode - * \warning This will insert a \c public: declaration at the head of the class - * or struct. While this is the default for struct, special care must be taken - * with class declarations since methods would otherwise be private by default. - * If you need to have a private section at the head of your class, make sure - * you use an explicit \c private: or \c protected: keyword as required. + * The compiler will check whether this is indeed needed, and fail with an + * appropriate warning if this is not true. In this case, you need to replace + * MEM_ALIGN with NO_MEM_ALIGN. While it is technically safe to use an + * over-aligned allocator in this case, it will impact performance and memory + * efficiency, and so is to be avoided. + * \sa NO_MEM_ALIGN + * \sa CHECK_MEM_ALIGN */ -#define MEMALIGN(classname) \ +#define MEM_ALIGN \ + void __check_memalign () { static_assert (alignof(*this) > MRTRIX_ALLOC_MEM_ALIGN, "no need to MEM_ALIGN: use NO_MEM_ALIGN here."); }\ public: \ - inline void* operator new (std::size_t size) { \ - if (alignof(classname) <= MRTRIX_ALLOC_MEMALIGN) return ::operator new (size); \ - std::cerr << __PRETTY_FUNCTION__ << ": new [" << size << "]\n"; \ - auto* original = std::malloc (size + alignof(classname)); \ - if (!original) throw std::bad_alloc(); \ - void *aligned = reinterpret_cast((reinterpret_cast(original) & ~(std::size_t(alignof(classname)-1))) + alignof(classname)); \ - *(reinterpret_cast(aligned) - 1) = original; \ - return aligned; \ - } \ - inline void* operator new[] (std::size_t size) { return classname::operator new (size); } \ - inline void operator delete(void* ptr) { \ - if (alignof(classname) <= MRTRIX_ALLOC_MEMALIGN) ::operator delete (ptr); \ - else { \ - std::cerr << __PRETTY_FUNCTION__ << ": delete\n"; \ - if (ptr) std::free (*(reinterpret_cast(ptr) - 1)); \ - } \ - } \ - inline void operator delete[](void* ptr, std::size_t sz) { classname::operator delete (ptr); } \ - + EIGEN_MAKE_ALIGNED_OPERATOR_NEW + +/*! \def CHECK_MEM_ALIGN + * used to verify that the class is set up approriately for memory alignment + * when dynamically allocated. This checks whether the class's alignment + * requirements exceed that of the default allocator, and if so whether it has + * custom operator new methods defined to deal with this. Conversely, it also + * checks whether a custom allocator has been defined needlessly, which is to + * be avoided for performance reasons. + * + * The compiler will check whether this is indeed needed, and fail with an + * appropriate warning if this is not true. In this case, you need to replace + * MEM_ALIGN with NO_MEM_ALIGN. + * \sa NO_MEM_ALIGN + * \sa MEM_ALIGN + */ +#define CHECK_MEM_ALIGN(classname) \ + static_assert ( (alignof(classname) <= MRTRIX_ALLOC_MEM_ALIGN ) || __has_custom_new_operator::value, \ + "memory alignment not guaranteed\n\n" \ + " Memory alignment requirement for this class is larger than that guaranteed\n" \ + " by default allocator, and no custom operator new has been defined.\n" \ + " This can cause unexpected runtime errors with Eigen.\n" \ + " Please replace NO_MEM_ALIGN with MEM_ALIGN for this class.\n"); \ + static_assert ( (alignof(classname) > MRTRIX_ALLOC_MEM_ALIGN ) || !__has_custom_new_operator::value, \ + "unnecessary use of MEM_ALIGN\n\n" \ + " Memory alignment requirement for this class is already catered for by default allocator.\n" \ + " While this program will run fine as-is, using the non-default allocator does incur\n" \ + " a performance overhead. Please replace MEM_ALIGN with NO_MEM_ALIGN for this class.\n") + + namespace MR { From 55589d9fd612e4b51efd1e1afb95162c008abd77 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Thu, 17 Nov 2016 14:13:54 +1100 Subject: [PATCH 234/723] moved lib/sparse to lib/formats/fixel --- cmd/fixel2sh.cpp | 6 +++--- cmd/fixel2tsf.cpp | 4 ++-- cmd/fixel2voxel.cpp | 6 +++--- cmd/fixelcfestats.cpp | 6 +++--- cmd/fixelconvert.cpp | 12 ++++++------ cmd/fixelcorrespondence.cpp | 4 ++-- cmd/fixelcrop.cpp | 4 ++-- cmd/fixelreorient.cpp | 4 ++-- cmd/fod2fixel.cpp | 4 ++-- cmd/tck2fixel.cpp | 6 +++--- cmd/voxel2fixel.cpp | 6 +++--- cmd/warp2metric.cpp | 6 +++--- lib/{sparse => formats/fixel}/helpers.h | 6 +++--- lib/{sparse => formats/fixel}/keys.h | 4 ++-- lib/{sparse => formats/fixel}/legacy/fixel_metric.h | 4 ++-- lib/{sparse => formats/fixel}/legacy/image.h | 8 ++++---- lib/{sparse => formats/fixel}/legacy/keys.h | 4 ++-- lib/{sparse => formats/fixel}/loop.h | 4 ++-- lib/formats/mrtrix_sparse_legacy.cpp | 2 +- lib/image_io/sparse.h | 2 +- src/dwi/tractography/SIFT/output.h | 6 +++--- src/dwi/tractography/SIFT/sifter.cpp | 2 +- src/dwi/tractography/SIFT2/tckfactor.cpp | 6 +++--- src/dwi/tractography/seeding/dynamic.cpp | 6 +++--- src/gui/mrview/tool/vector/fixel.h | 6 +++--- testing/cmd/testing_diff_fixel.cpp | 2 +- testing/cmd/testing_diff_fixel_old.cpp | 4 ++-- 27 files changed, 67 insertions(+), 67 deletions(-) rename lib/{sparse => formats/fixel}/helpers.h (99%) rename lib/{sparse => formats/fixel}/keys.h (91%) rename lib/{sparse => formats/fixel}/legacy/fixel_metric.h (94%) rename lib/{sparse => formats/fixel}/legacy/image.h (96%) rename lib/{sparse => formats/fixel}/legacy/keys.h (91%) rename lib/{sparse => formats/fixel}/loop.h (97%) diff --git a/cmd/fixel2sh.cpp b/cmd/fixel2sh.cpp index b2c17eeb0a..c2f6a23af7 100644 --- a/cmd/fixel2sh.cpp +++ b/cmd/fixel2sh.cpp @@ -22,9 +22,9 @@ #include "math/SH.h" -#include "sparse/helpers.h" -#include "sparse/keys.h" -#include "sparse/loop.h" +#include "formats/fixel/helpers.h" +#include "formats/fixel/keys.h" +#include "formats/fixel/loop.h" using namespace MR; using namespace App; diff --git a/cmd/fixel2tsf.cpp b/cmd/fixel2tsf.cpp index c3b7a1b33a..378d9d3285 100644 --- a/cmd/fixel2tsf.cpp +++ b/cmd/fixel2tsf.cpp @@ -19,8 +19,8 @@ #include "algo/loop.h" #include "image.h" -#include "sparse/helpers.h" -#include "sparse/keys.h" +#include "formats/fixel/helpers.h" +#include "formats/fixel/keys.h" #include "dwi/tractography/file.h" #include "dwi/tractography/scalar_file.h" diff --git a/cmd/fixel2voxel.cpp b/cmd/fixel2voxel.cpp index ce08472210..446d1315e5 100644 --- a/cmd/fixel2voxel.cpp +++ b/cmd/fixel2voxel.cpp @@ -26,9 +26,9 @@ #include "math/SH.h" -#include "sparse/helpers.h" -#include "sparse/keys.h" -#include "sparse/loop.h" +#include "formats/fixel/helpers.h" +#include "formats/fixel/keys.h" +#include "formats/fixel/loop.h" using namespace MR; using namespace App; diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 2788e19df3..60ae2739a3 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -19,9 +19,9 @@ #include "algo/loop.h" #include "transform.h" #include "image.h" -#include "sparse/helpers.h" -#include "sparse/keys.h" -#include "sparse/loop.h" +#include "formats/fixel/helpers.h" +#include "formats/fixel/keys.h" +#include "formats/fixel/loop.h" #include "math/stats/glm.h" #include "math/stats/permutation.h" #include "math/stats/typedefs.h" diff --git a/cmd/fixelconvert.cpp b/cmd/fixelconvert.cpp index 686103f160..21fe4fdb27 100644 --- a/cmd/fixelconvert.cpp +++ b/cmd/fixelconvert.cpp @@ -22,13 +22,13 @@ #include "math/SH.h" -#include "sparse/helpers.h" -#include "sparse/keys.h" -#include "sparse/loop.h" +#include "formats/fixel/helpers.h" +#include "formats/fixel/keys.h" +#include "formats/fixel/loop.h" -#include "sparse/legacy/fixel_metric.h" -#include "sparse/legacy/keys.h" -#include "sparse/legacy/image.h" +#include "formats/fixel/legacy/fixel_metric.h" +#include "formats/fixel/legacy/keys.h" +#include "formats/fixel/legacy/image.h" using namespace MR; diff --git a/cmd/fixelcorrespondence.cpp b/cmd/fixelcorrespondence.cpp index 2e151eb4a5..3844574521 100644 --- a/cmd/fixelcorrespondence.cpp +++ b/cmd/fixelcorrespondence.cpp @@ -18,8 +18,8 @@ #include "progressbar.h" #include "algo/loop.h" #include "image.h" -#include "sparse/helpers.h" -#include "sparse/keys.h" +#include "formats/fixel/helpers.h" +#include "formats/fixel/keys.h" using namespace MR; using namespace App; diff --git a/cmd/fixelcrop.cpp b/cmd/fixelcrop.cpp index 68400ae014..c5ebf2cc69 100644 --- a/cmd/fixelcrop.cpp +++ b/cmd/fixelcrop.cpp @@ -20,8 +20,8 @@ #include "image.h" -#include "sparse/helpers.h" -#include "sparse/keys.h" +#include "formats/fixel/helpers.h" +#include "formats/fixel/keys.h" using namespace MR; using namespace App; diff --git a/cmd/fixelreorient.cpp b/cmd/fixelreorient.cpp index 8f139f46f8..e183355656 100644 --- a/cmd/fixelreorient.cpp +++ b/cmd/fixelreorient.cpp @@ -24,8 +24,8 @@ using namespace MR; using namespace App; -#include "sparse/helpers.h" -#include "sparse/keys.h" +#include "formats/fixel/helpers.h" +#include "formats/fixel/keys.h" void usage () { diff --git a/cmd/fod2fixel.cpp b/cmd/fod2fixel.cpp index a985807baf..ae4784634f 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -19,8 +19,8 @@ #include "image.h" -#include "sparse/keys.h" -#include "sparse/helpers.h" +#include "formats/fixel/keys.h" +#include "formats/fixel/helpers.h" #include "math/SH.h" diff --git a/cmd/tck2fixel.cpp b/cmd/tck2fixel.cpp index caf5012c5d..f6d76c6246 100644 --- a/cmd/tck2fixel.cpp +++ b/cmd/tck2fixel.cpp @@ -19,9 +19,9 @@ #include "algo/loop.h" #include "image.h" -#include "sparse/helpers.h" -#include "sparse/keys.h" -#include "sparse/loop.h" +#include "formats/fixel/helpers.h" +#include "formats/fixel/keys.h" +#include "formats/fixel/loop.h" #include "dwi/tractography/mapping/mapper.h" #include "dwi/tractography/mapping/loader.h" diff --git a/cmd/voxel2fixel.cpp b/cmd/voxel2fixel.cpp index 126db10ce8..d6f03240fd 100644 --- a/cmd/voxel2fixel.cpp +++ b/cmd/voxel2fixel.cpp @@ -19,9 +19,9 @@ #include "algo/loop.h" #include "image.h" -#include "sparse/helpers.h" -#include "sparse/keys.h" -#include "sparse/loop.h" +#include "formats/fixel/helpers.h" +#include "formats/fixel/keys.h" +#include "formats/fixel/loop.h" using namespace MR; using namespace App; diff --git a/cmd/warp2metric.cpp b/cmd/warp2metric.cpp index fc01919345..55c9905bc4 100644 --- a/cmd/warp2metric.cpp +++ b/cmd/warp2metric.cpp @@ -20,9 +20,9 @@ #include "image.h" #include "adapter/jacobian.h" #include "registration/warp/helpers.h" -#include "sparse/helpers.h" -#include "sparse/keys.h" -#include "sparse/loop.h" +#include "formats/fixel/helpers.h" +#include "formats/fixel/keys.h" +#include "formats/fixel/loop.h" using namespace MR; using namespace App; diff --git a/lib/sparse/helpers.h b/lib/formats/fixel/helpers.h similarity index 99% rename from lib/sparse/helpers.h rename to lib/formats/fixel/helpers.h index 81f75563ab..7e6f67bf8c 100644 --- a/lib/sparse/helpers.h +++ b/lib/formats/fixel/helpers.h @@ -13,11 +13,11 @@ * */ -#ifndef __sparse_helpers_h__ -#define __sparse_helpers_h__ +#ifndef __formats_fixel_helpers_h__ +#define __formats_fixel_helpers_h__ #include "formats/mrtrix_utils.h" -#include "sparse/keys.h" +#include "formats/fixel/keys.h" #include "algo/loop.h" #include "image_diff.h" diff --git a/lib/sparse/keys.h b/lib/formats/fixel/keys.h similarity index 91% rename from lib/sparse/keys.h rename to lib/formats/fixel/keys.h index eb168769bc..58ddf0e613 100644 --- a/lib/sparse/keys.h +++ b/lib/formats/fixel/keys.h @@ -13,8 +13,8 @@ * */ -#ifndef __sparse_keys_h__ -#define __sparse_keys_h__ +#ifndef __formats_fixel_keys_h__ +#define __formats_fixel_keys_h__ #include diff --git a/lib/sparse/legacy/fixel_metric.h b/lib/formats/fixel/legacy/fixel_metric.h similarity index 94% rename from lib/sparse/legacy/fixel_metric.h rename to lib/formats/fixel/legacy/fixel_metric.h index 1777550b41..903edfa447 100644 --- a/lib/sparse/legacy/fixel_metric.h +++ b/lib/formats/fixel/legacy/fixel_metric.h @@ -13,8 +13,8 @@ * */ -#ifndef __sparse_legacy_metric_h__ -#define __sparse_legacy_metric_h__ +#ifndef __formats_fixel_legacy_metric_h__ +#define __formats_fixel_legacy_metric_h__ #include "types.h" diff --git a/lib/sparse/legacy/image.h b/lib/formats/fixel/legacy/image.h similarity index 96% rename from lib/sparse/legacy/image.h rename to lib/formats/fixel/legacy/image.h index aac230b77b..339773f54c 100644 --- a/lib/sparse/legacy/image.h +++ b/lib/formats/fixel/legacy/image.h @@ -13,18 +13,18 @@ * */ -#ifndef __sparse_legacy_image_h__ -#define __sparse_legacy_image_h__ +#ifndef __formats_fixel_legacy_image_h__ +#define __formats_fixel_legacy_image_h__ #include #include "image.h" #include "header.h" #include "image_io/sparse.h" -#include "sparse/legacy/keys.h" +#include "formats/fixel/legacy/keys.h" #ifndef __image_h__ -#error File that #includes "sparse/legacy/image.h" must explicitly #include "image.h" beforehand +#error File that #includes "formats/fixel/legacy/image.h" must explicitly #include "image.h" beforehand #endif diff --git a/lib/sparse/legacy/keys.h b/lib/formats/fixel/legacy/keys.h similarity index 91% rename from lib/sparse/legacy/keys.h rename to lib/formats/fixel/legacy/keys.h index 7b55334f0a..36944acde2 100644 --- a/lib/sparse/legacy/keys.h +++ b/lib/formats/fixel/legacy/keys.h @@ -13,8 +13,8 @@ * */ -#ifndef __sparse_legacy_keys_h__ -#define __sparse_legacy_keys_h__ +#ifndef __formats_fixel_legacy_keys_h__ +#define __formats_fixel_legacy_keys_h__ #include diff --git a/lib/sparse/loop.h b/lib/formats/fixel/loop.h similarity index 97% rename from lib/sparse/loop.h rename to lib/formats/fixel/loop.h index f84b5bd31f..03a4a57e35 100644 --- a/lib/sparse/loop.h +++ b/lib/formats/fixel/loop.h @@ -13,8 +13,8 @@ * */ -#ifndef __sparse_loop_h__ -#define __sparse_loop_h__ +#ifndef __formats_fixel_loop_h__ +#define __formats_fixel_loop_h__ #include "formats/mrtrix_utils.h" diff --git a/lib/formats/mrtrix_sparse_legacy.cpp b/lib/formats/mrtrix_sparse_legacy.cpp index aa41e502c0..0642cefab9 100644 --- a/lib/formats/mrtrix_sparse_legacy.cpp +++ b/lib/formats/mrtrix_sparse_legacy.cpp @@ -26,7 +26,7 @@ #include "image_io/sparse.h" #include "formats/list.h" #include "formats/mrtrix_utils.h" -#include "sparse/legacy/keys.h" +#include "formats/fixel/legacy/keys.h" diff --git a/lib/image_io/sparse.h b/lib/image_io/sparse.h index 0e6eb5ebeb..34e25f5205 100644 --- a/lib/image_io/sparse.h +++ b/lib/image_io/sparse.h @@ -41,7 +41,7 @@ namespace MR // A quick description of how the sparse image data are currently stored: // * The data are either after the image data within the same file if extension is .msf, or // in a separate file with the .sdat extension if the image extension if .msh - // * The image header must store the fields defined in lib/image/sparse/key.h + // * The image header must store the fields defined in lib/image/formats/fixel/key.h // These are currently verified on construction of the BufferSparse class. This proved to // be simpler than trying to verify class matching on every interaction with the handler // using templated functions. diff --git a/src/dwi/tractography/SIFT/output.h b/src/dwi/tractography/SIFT/output.h index ce8da7b9e3..7aaf92f66b 100644 --- a/src/dwi/tractography/SIFT/output.h +++ b/src/dwi/tractography/SIFT/output.h @@ -31,9 +31,9 @@ #include "math/SH.h" -#include "sparse/legacy/fixel_metric.h" -#include "sparse/legacy/image.h" -#include "sparse/legacy/keys.h" +#include "formats/fixel/legacy/fixel_metric.h" +#include "formats/fixel/legacy/image.h" +#include "formats/fixel/legacy/keys.h" namespace MR diff --git a/src/dwi/tractography/SIFT/sifter.cpp b/src/dwi/tractography/SIFT/sifter.cpp index acf7ffdda3..d9e9d6261f 100644 --- a/src/dwi/tractography/SIFT/sifter.cpp +++ b/src/dwi/tractography/SIFT/sifter.cpp @@ -34,7 +34,7 @@ #include "math/rng.h" -#include "sparse/legacy/image.h" +#include "formats/fixel/legacy/image.h" diff --git a/src/dwi/tractography/SIFT2/tckfactor.cpp b/src/dwi/tractography/SIFT2/tckfactor.cpp index 3f8a944a6d..78c9cbde78 100644 --- a/src/dwi/tractography/SIFT2/tckfactor.cpp +++ b/src/dwi/tractography/SIFT2/tckfactor.cpp @@ -20,9 +20,9 @@ #include "math/math.h" -#include "sparse/legacy/fixel_metric.h" -#include "sparse/legacy/image.h" -#include "sparse/legacy/keys.h" +#include "formats/fixel/legacy/fixel_metric.h" +#include "formats/fixel/legacy/image.h" +#include "formats/fixel/legacy/keys.h" #include "dwi/tractography/SIFT2/coeff_optimiser.h" #include "dwi/tractography/SIFT2/fixel_updater.h" diff --git a/src/dwi/tractography/seeding/dynamic.cpp b/src/dwi/tractography/seeding/dynamic.cpp index 2cacd150e2..cbb54c7cb0 100644 --- a/src/dwi/tractography/seeding/dynamic.cpp +++ b/src/dwi/tractography/seeding/dynamic.cpp @@ -21,9 +21,9 @@ #include "dwi/tractography/rng.h" #include "dwi/tractography/seeding/dynamic.h" -#include "sparse/legacy/fixel_metric.h" -#include "sparse/legacy/keys.h" -#include "sparse/legacy/image.h" +#include "formats/fixel/legacy/fixel_metric.h" +#include "formats/fixel/legacy/keys.h" +#include "formats/fixel/legacy/image.h" diff --git a/src/gui/mrview/tool/vector/fixel.h b/src/gui/mrview/tool/vector/fixel.h index d41d933429..029a46de8c 100644 --- a/src/gui/mrview/tool/vector/fixel.h +++ b/src/gui/mrview/tool/vector/fixel.h @@ -22,9 +22,9 @@ #include "transform.h" #include "algo/loop.h" -#include "sparse/legacy/image.h" -#include "sparse/legacy/fixel_metric.h" -#include "sparse/helpers.h" +#include "formats/fixel/legacy/image.h" +#include "formats/fixel/legacy/fixel_metric.h" +#include "formats/fixel/helpers.h" #include "gui/mrview/displayable.h" #include "gui/mrview/tool/vector/vector.h" diff --git a/testing/cmd/testing_diff_fixel.cpp b/testing/cmd/testing_diff_fixel.cpp index e25a37e548..e61c4ac360 100644 --- a/testing/cmd/testing_diff_fixel.cpp +++ b/testing/cmd/testing_diff_fixel.cpp @@ -18,7 +18,7 @@ #include "image.h" -#include "sparse/helpers.h" +#include "formats/fixel/helpers.h" #include "diff_images.h" diff --git a/testing/cmd/testing_diff_fixel_old.cpp b/testing/cmd/testing_diff_fixel_old.cpp index 6cc1c77534..22106c57cb 100644 --- a/testing/cmd/testing_diff_fixel_old.cpp +++ b/testing/cmd/testing_diff_fixel_old.cpp @@ -21,8 +21,8 @@ #include "image.h" -#include "sparse/image.h" -#include "sparse/fixel_metric.h" +#include "formats/fixel/image.h" +#include "formats/fixel/fixel_metric.h" #include "image_helpers.h" #include "algo/threaded_loop.h" using MR::Sparse::Legacy::FixelMetric; From 5128e964b7f5ef37d3d920b0efcef38eb28fc08e Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Fri, 18 Nov 2016 15:58:08 +1100 Subject: [PATCH 235/723] Initial change of sparse format naming and restructure --- cmd/fixel2sh.cpp | 14 +++--- cmd/fixel2tsf.cpp | 10 ++-- cmd/fixel2voxel.cpp | 46 +++++++++---------- cmd/fixelcfestats.cpp | 20 ++++---- cmd/fixelconvert.cpp | 46 +++++++++---------- cmd/fixelcorrespondence.cpp | 16 +++---- cmd/fixelcrop.cpp | 18 ++++---- cmd/fixelreorient.cpp | 16 +++---- cmd/fod2fixel.cpp | 16 +++---- cmd/tck2fixel.cpp | 18 ++++---- cmd/voxel2fixel.cpp | 16 +++---- cmd/warp2metric.cpp | 18 ++++---- lib/{formats => }/fixel/helpers.h | 22 ++++----- lib/{formats => }/fixel/keys.h | 6 +-- lib/{formats => }/fixel/legacy/fixel_metric.h | 8 ++-- lib/{formats => }/fixel/legacy/image.h | 18 ++++---- lib/{formats => }/fixel/legacy/keys.h | 8 ++-- lib/{formats => }/fixel/loop.h | 9 ++-- lib/formats/mrtrix_sparse_legacy.cpp | 14 +++--- lib/image_io/sparse.cpp | 2 +- lib/image_io/sparse.h | 2 +- src/dwi/tractography/SIFT/output.h | 32 ++++++------- src/dwi/tractography/SIFT/sifter.cpp | 2 +- src/dwi/tractography/SIFT2/tckfactor.cpp | 28 +++++------ src/dwi/tractography/seeding/dynamic.cpp | 30 ++++++------ src/gui/mrview/tool/vector/fixel.h | 8 ++-- src/gui/mrview/tool/vector/fixelfolder.cpp | 6 +-- src/gui/mrview/tool/vector/fixelfolder.h | 2 +- testing/cmd/testing_diff_fixel.cpp | 6 +-- testing/cmd/testing_diff_fixel_old.cpp | 10 ++-- 30 files changed, 231 insertions(+), 236 deletions(-) rename lib/{formats => }/fixel/helpers.h (95%) rename lib/{formats => }/fixel/keys.h (89%) rename lib/{formats => }/fixel/legacy/fixel_metric.h (93%) rename lib/{formats => }/fixel/legacy/image.h (93%) rename lib/{formats => }/fixel/legacy/keys.h (89%) rename lib/{formats => }/fixel/loop.h (94%) diff --git a/cmd/fixel2sh.cpp b/cmd/fixel2sh.cpp index c2f6a23af7..b030fd7e97 100644 --- a/cmd/fixel2sh.cpp +++ b/cmd/fixel2sh.cpp @@ -22,9 +22,9 @@ #include "math/SH.h" -#include "formats/fixel/helpers.h" -#include "formats/fixel/keys.h" -#include "formats/fixel/loop.h" +#include "fixel/helpers.h" +#include "fixel/keys.h" +#include "fixel/loop.h" using namespace MR; using namespace App; @@ -53,11 +53,11 @@ void usage () void run () { - auto in_data_image = Sparse::open_fixel_data_file (argument[0]); + auto in_data_image = Fixel::open_fixel_data_file (argument[0]); - Header in_index_header = Sparse::find_index_header (Sparse::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 = Sparse::find_directions_header (Sparse::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(); size_t lmax = 8; auto opt = get_options ("lmax"); @@ -78,7 +78,7 @@ void run () 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 = Sparse::FixelLoop (in_index_image) (in_directions_image, in_data_image); f; ++f) { + 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) diff --git a/cmd/fixel2tsf.cpp b/cmd/fixel2tsf.cpp index 378d9d3285..5c07ab2bd8 100644 --- a/cmd/fixel2tsf.cpp +++ b/cmd/fixel2tsf.cpp @@ -19,8 +19,8 @@ #include "algo/loop.h" #include "image.h" -#include "formats/fixel/helpers.h" -#include "formats/fixel/keys.h" +#include "fixel/helpers.h" +#include "fixel/keys.h" #include "dwi/tractography/file.h" #include "dwi/tractography/scalar_file.h" @@ -65,14 +65,14 @@ typedef DWI::Tractography::Mapping::SetVoxelDir SetVoxelDir; void run () { - auto in_data_image = Sparse::open_fixel_data_file (argument[0]); + 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"); - Header in_index_header = Sparse::find_index_header (Sparse::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 = Sparse::find_directions_header (Sparse::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); diff --git a/cmd/fixel2voxel.cpp b/cmd/fixel2voxel.cpp index 446d1315e5..bfca38cd4c 100644 --- a/cmd/fixel2voxel.cpp +++ b/cmd/fixel2voxel.cpp @@ -26,9 +26,9 @@ #include "math/SH.h" -#include "formats/fixel/helpers.h" -#include "formats/fixel/keys.h" -#include "formats/fixel/loop.h" +#include "fixel/helpers.h" +#include "fixel/keys.h" +#include "fixel/loop.h" using namespace MR; using namespace App; @@ -99,7 +99,7 @@ class Mean default_type sum = 0.0; default_type sum_volumes = 0.0; if (vol.valid()) { - for (auto f = Sparse::FixelLoop (index) (data, vol); f; ++f) { + for (auto f = Fixel::Loop (index) (data, vol); f; ++f) { sum += data.value() * vol.value(); sum_volumes += vol.value(); } @@ -107,7 +107,7 @@ class Mean } else { index.index(3) = 0; size_t num_fixels = index.value(); - for (auto f = Sparse::FixelLoop (index) (data); f; ++f) + for (auto f = Fixel::Loop (index) (data); f; ++f) sum += data.value(); out.value() = num_fixels ? (sum / default_type(num_fixels)) : 0.0; } @@ -127,10 +127,10 @@ class Sum { if (vol.valid()) { - for (auto f = Sparse::FixelLoop (index) (data, vol); f; ++f) + for (auto f = Fixel::Loop (index) (data, vol); f; ++f) out.value() += data.value() * vol.value(); } else { - for (auto f = Sparse::FixelLoop (index) (data); f; ++f) + for (auto f = Fixel::Loop (index) (data); f; ++f) out.value() += data.value(); } } @@ -176,7 +176,7 @@ class Min void operator() (Image& index, Image& out) { default_type min = std::numeric_limits::infinity(); - for (auto f = Sparse::FixelLoop (index) (data); f; ++f) { + for (auto f = Fixel::Loop (index) (data); f; ++f) { if (data.value() < min) min = data.value(); } @@ -196,7 +196,7 @@ class Max void operator() (Image& index, Image& out) { default_type max = -std::numeric_limits::infinity(); - for (auto f = Sparse::FixelLoop (index) (data); f; ++f) { + for (auto f = Fixel::Loop (index) (data); f; ++f) { if (data.value() > max) max = data.value(); } @@ -216,7 +216,7 @@ class AbsMax void operator() (Image& index, Image& out) { default_type absmax = -std::numeric_limits::infinity(); - for (auto f = Sparse::FixelLoop (index) (data); f; ++f) { + for (auto f = Fixel::Loop (index) (data); f; ++f) { if (std::abs (data.value()) > absmax) absmax = std::abs (data.value()); } @@ -236,7 +236,7 @@ class MagMax void operator() (Image& index, Image& out) { default_type magmax = 0.0; - for (auto f = Sparse::FixelLoop (index) (data); f; ++f) { + for (auto f = Fixel::Loop (index) (data); f; ++f) { if (std::abs (data.value()) > std::abs (magmax)) magmax = data.value(); } @@ -263,7 +263,7 @@ class Complexity } default_type max = 0.0; default_type sum = 0.0; - for (auto f = Sparse::FixelLoop (index) (data); f; ++f) { + for (auto f = Fixel::Loop (index) (data); f; ++f) { max = std::max (max, default_type(data.value())); sum += data.value(); } @@ -283,7 +283,7 @@ class SF { default_type max = 0.0; default_type sum = 0.0; - for (auto f = Sparse::FixelLoop (index) (data); f; ++f) { + for (auto f = Fixel::Loop (index) (data); f; ++f) { max = std::max (max, default_type(data.value())); sum += data.value(); } @@ -304,10 +304,10 @@ class DEC_unit { Eigen::Vector3 sum_dec = {0.0, 0.0, 0.0}; if (vol.valid()) { - for (auto f = Sparse::FixelLoop (index) (data, vol, dir); f; ++f) + for (auto f = Fixel::Loop (index) (data, vol, dir); f; ++f) sum_dec += Eigen::Vector3 (std::abs (dir.row(1)[0]), std::abs (dir.row(1)[1]), std::abs (dir.row(1)[2])) * data.value() * vol.value(); } else { - for (auto f = Sparse::FixelLoop (index) (data, dir); f; ++f) + for (auto f = Fixel::Loop (index) (data, dir); f; ++f) sum_dec += Eigen::Vector3 (std::abs (dir.row(1)[0]), std::abs (dir.row(1)[1]), std::abs (dir.row(1)[2])) * data.value(); } if ((sum_dec.array() != 0.0).any()) @@ -332,7 +332,7 @@ class DEC_scaled default_type sum_value = 0.0; if (vol.valid()) { default_type sum_volume = 0.0; - for (auto f = Sparse::FixelLoop (index) (data, vol, dir); f; ++f) { + for (auto f = Fixel::Loop (index) (data, vol, dir); f; ++f) { sum_dec += Eigen::Vector3 (std::abs (dir.row(1)[0]), std::abs (dir.row(1)[1]), std::abs (dir.row(1)[2])) * data.value() * vol.value(); sum_volume += vol.value(); sum_value += vol.value() * data.value(); @@ -341,7 +341,7 @@ class DEC_scaled sum_dec.normalize(); sum_dec *= (sum_value / sum_volume); } else { - for (auto f = Sparse::FixelLoop (index) (data, dir); f; ++f) { + for (auto f = Fixel::Loop (index) (data, dir); f; ++f) { sum_dec += Eigen::Vector3 (std::abs (dir.row(1)[0]), std::abs (dir.row(1)[1]), std::abs (dir.row(1)[2])) * data.value(); sum_value += data.value(); } @@ -390,7 +390,7 @@ class SplitDir void operator() (Image& index, Image& out) { out.index(3) = 0; - for (auto f = Sparse::FixelLoop (index) (dir); f; ++f) { + for (auto f = Fixel::Loop (index) (dir); f; ++f) { for (size_t axis = 0; axis < 3; ++axis) { dir.index(1) = axis; out.value() = dir.value(); @@ -412,11 +412,11 @@ class SplitDir void run () { - auto in_data = Sparse::open_fixel_data_file (argument[0]); + 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)"); - Header in_index_header = Sparse::find_index_header (Sparse::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; @@ -426,7 +426,7 @@ void run () Header H_out (in_index_header); H_out.datatype() = DataType::Float32; H_out.datatype().set_byte_order_native(); - H_out.keyval().erase (Sparse::n_fixels_key); + H_out.keyval().erase (Fixel::n_fixels_key); if (op == 7) { // count H_out.datatype() = DataType::UInt8; } else if (op == 10 || op == 11) { // dec @@ -445,8 +445,8 @@ void run () } if (op == 10 || op == 11 || op == 13) // dec or split_dir - in_directions = Sparse::find_directions_header ( - Sparse::get_fixel_directory (in_data.name())).get_image().with_direct_io(); + in_directions = Fixel::find_directions_header ( + Fixel::get_fixel_directory (in_data.name())).get_image().with_direct_io(); Image in_vol; auto opt = get_options ("weighted"); diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 60ae2739a3..b7b4bfe477 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -19,9 +19,9 @@ #include "algo/loop.h" #include "transform.h" #include "image.h" -#include "formats/fixel/helpers.h" -#include "formats/fixel/keys.h" -#include "formats/fixel/loop.h" +#include "fixel/helpers.h" +#include "fixel/keys.h" +#include "fixel/loop.h" #include "math/stats/glm.h" #include "math/stats/permutation.h" #include "math/stats/typedefs.h" @@ -148,20 +148,20 @@ void run() { const std::string input_fixel_directory = argument[0]; - Header index_header = Sparse::find_index_header (input_fixel_directory); + Header index_header = Fixel::find_index_header (input_fixel_directory); auto index_image = index_header.get_image(); - const uint32_t num_fixels = Sparse::get_number_of_fixels (index_header); + const uint32_t num_fixels = Fixel::get_number_of_fixels (index_header); CONSOLE ("number of fixels: " + str(num_fixels)); std::vector positions (num_fixels); std::vector directions (num_fixels); const std::string output_fixel_directory = argument[5]; - Sparse::copy_index_and_directions_file (input_fixel_directory, output_fixel_directory); + Fixel::copy_index_and_directions_file (input_fixel_directory, output_fixel_directory); { - auto directions_data = Sparse::find_directions_header (input_fixel_directory).get_image().with_direct_io ({+2,+1}); + auto directions_data = Fixel::find_directions_header (input_fixel_directory).get_image().with_direct_io ({+2,+1}); // 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) { @@ -169,7 +169,7 @@ void run() { index_image.index(3) = 1; uint32_t offset = index_image.value(); size_t fixel_index = 0; - for (auto f = Sparse::FixelLoop (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; } @@ -190,7 +190,7 @@ void run() { if (!MR::Path::exists (filename)) throw Exception ("input fixel image not found: " + filename); header = Header::open (filename); - Sparse::fixels_match (index_header, header); + Fixel::fixels_match (index_header, header); identifiers.push_back (filename); progress++; } @@ -332,7 +332,7 @@ void run() { index_image.index(3) = 1; uint32_t offset = index_image.value(); uint32_t fixel_index = 0; - for (auto f = Sparse::FixelLoop (index_image) (subject_data); f; ++f, ++fixel_index) { + for (auto f = Fixel::Loop (index_image) (subject_data); f; ++f, ++fixel_index) { if (!std::isfinite(subject_data.value())) throw Exception ("subject data file " + identifiers[subject] + " contains non-finite value: " + str(subject_data.value())); subject_data_vector[offset + fixel_index] = subject_data.value(); diff --git a/cmd/fixelconvert.cpp b/cmd/fixelconvert.cpp index 21fe4fdb27..7e2c459fc0 100644 --- a/cmd/fixelconvert.cpp +++ b/cmd/fixelconvert.cpp @@ -22,19 +22,19 @@ #include "math/SH.h" -#include "formats/fixel/helpers.h" -#include "formats/fixel/keys.h" -#include "formats/fixel/loop.h" +#include "fixel/helpers.h" +#include "fixel/keys.h" +#include "fixel/loop.h" -#include "formats/fixel/legacy/fixel_metric.h" -#include "formats/fixel/legacy/keys.h" -#include "formats/fixel/legacy/image.h" +#include "fixel/legacy/fixel_metric.h" +#include "fixel/legacy/keys.h" +#include "fixel/legacy/image.h" using namespace MR; using namespace App; -using Sparse::Legacy::FixelMetric; +using Fixel::Legacy::FixelMetric; void usage () @@ -72,10 +72,10 @@ void usage () void convert_old2new () { Header header (Header::open (argument[0])); - header.keyval().erase (Sparse::Legacy::name_key); - header.keyval().erase (Sparse::Legacy::size_key); + header.keyval().erase (Fixel::Legacy::name_key); + header.keyval().erase (Fixel::Legacy::size_key); - Sparse::Legacy::Image input (argument[0]); + Fixel::Legacy::Image input (argument[0]); const std::string file_extension = get_options ("nii").size() ? ".nii" : ".mif"; @@ -87,7 +87,7 @@ void convert_old2new () const bool output_size = get_options ("out_size").size(); const std::string output_fixel_directory = argument[1]; - Sparse::check_fixel_directory (output_fixel_directory, true); + Fixel::check_fixel_directory (output_fixel_directory, true); uint32_t fixel_count = 0; for (auto i = Loop (input) (input); i; ++i) @@ -104,7 +104,7 @@ void convert_old2new () Header directions_header (data_header); directions_header.size(1) = 3; - header.keyval()[Sparse::n_fixels_key] = str(fixel_count); + header.keyval()[Fixel::n_fixels_key] = str(fixel_count); header.ndim() = 4; header.size(3) = 2; header.datatype() = DataType::from(); @@ -121,10 +121,10 @@ void convert_old2new () Image template_directions_image; opt = get_options ("template"); if (opt.size()) { - Sparse::check_fixel_directory (opt[0][0]); - template_index_image = Sparse::find_index_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 = Sparse::find_directions_header (opt[0][0]).get_image(); + template_directions_image = Fixel::find_directions_header (opt[0][0]).get_image(); } uint32_t offset = 0; @@ -181,9 +181,9 @@ void convert_new2old () opt = get_options ("in_size"); const std::string size_path = opt.size() ? std::string(opt[0][0]) : ""; - Header H_index = Sparse::find_index_header (input_fixel_directory); - Header H_dirs = Sparse::find_directions_header (input_fixel_directory); - std::vector
H_data = Sparse::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); + std::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) { @@ -199,9 +199,9 @@ void convert_new2old () H_out.ndim() = 3; H_out.datatype() = DataType::UInt64; H_out.datatype().set_byte_order_native(); - H_out.keyval()[Sparse::Legacy::name_key] = str(typeid(FixelMetric).name()); - H_out.keyval()[Sparse::Legacy::size_key] = str(sizeof(FixelMetric)); - Sparse::Legacy::Image out_image (argument[1], H_out); + 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); auto index_image = H_index.get_image(); auto dirs_image = H_dirs.get_image(); @@ -214,14 +214,14 @@ void convert_new2old () index_image.index(3) = 0; const uint32_t num_fixels = index_image.value(); out_image.value().set_size (num_fixels); - for (auto f = Sparse::FixelLoop (index_image) (dirs_image, value_image); f; ++f) { + 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(); } - Sparse::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); fixel.size = size_image.value(); diff --git a/cmd/fixelcorrespondence.cpp b/cmd/fixelcorrespondence.cpp index 3844574521..e24579e2c7 100644 --- a/cmd/fixelcorrespondence.cpp +++ b/cmd/fixelcorrespondence.cpp @@ -18,8 +18,8 @@ #include "progressbar.h" #include "algo/loop.h" #include "image.h" -#include "formats/fixel/helpers.h" -#include "formats/fixel/keys.h" +#include "fixel/helpers.h" +#include "fixel/keys.h" using namespace MR; using namespace App; @@ -56,21 +56,21 @@ void run () 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 = Sparse::find_index_header (Sparse::get_fixel_directory (input_file)).get_image(); - auto subject_directions = Sparse::find_directions_header (Sparse::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"); auto subject_data = Image::open (input_file); - Sparse::check_fixel_size (subject_index, subject_data); + Fixel::check_fixel_size (subject_index, subject_data); - auto template_index = Sparse::find_index_header (argument[1]).get_image(); - auto template_directions = Sparse::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); std::string output_fixel_directory = argument[2]; - Sparse::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); output_data_header.size(1) = 1; diff --git a/cmd/fixelcrop.cpp b/cmd/fixelcrop.cpp index c5ebf2cc69..dbb707eb2b 100644 --- a/cmd/fixelcrop.cpp +++ b/cmd/fixelcrop.cpp @@ -20,8 +20,8 @@ #include "image.h" -#include "formats/fixel/helpers.h" -#include "formats/fixel/keys.h" +#include "fixel/helpers.h" +#include "fixel/keys.h" using namespace MR; using namespace App; @@ -48,18 +48,18 @@ void usage () void run () { const auto in_directory = argument[0]; - Sparse::check_fixel_directory (in_directory); - Header in_index_header = Sparse::find_index_header (in_directory); + 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]); - Sparse::check_fixel_size (in_index_image, mask_image); + Fixel::check_fixel_size (in_index_image, mask_image); const auto out_fixel_directory = argument[2]; - Sparse::check_fixel_directory (out_fixel_directory, true); + Fixel::check_fixel_directory (out_fixel_directory, true); Header out_header = Header (in_index_image); - size_t total_nfixels = std::stoul (out_header.keyval ()[Sparse::n_fixels_key]); + size_t total_nfixels = std::stoul (out_header.keyval ()[Fixel::n_fixels_key]); // 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) { @@ -67,12 +67,12 @@ void run () total_nfixels --; } - out_header.keyval ()[Sparse::n_fixels_key] = str (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); // Open all data images and create output date images with size equal to expected number of fixels - std::vector
in_headers = Sparse::find_data_headers (in_directory, in_index_header, true); + std::vector
in_headers = Fixel::find_data_headers (in_directory, in_index_header, true); std::vector > in_data_images; std::vector > out_data_images; for (auto& in_data_header : in_headers) { diff --git a/cmd/fixelreorient.cpp b/cmd/fixelreorient.cpp index e183355656..a05037a77c 100644 --- a/cmd/fixelreorient.cpp +++ b/cmd/fixelreorient.cpp @@ -24,8 +24,8 @@ using namespace MR; using namespace App; -#include "formats/fixel/helpers.h" -#include "formats/fixel/keys.h" +#include "fixel/helpers.h" +#include "fixel/keys.h" void usage () { @@ -50,9 +50,9 @@ void usage () void run () { std::string input_fixel_directory = argument[0]; - Sparse::check_fixel_directory (input_fixel_directory); + Fixel::check_fixel_directory (input_fixel_directory); - auto input_index_image = Sparse::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); @@ -60,13 +60,13 @@ void run () Adapter::Jacobian > jacobian (warp_header.get_image()); std::string output_fixel_directory = argument[2]; - Sparse::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 = Sparse::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); output_directions_filename = Path::basename(tmp.name()); @@ -91,8 +91,8 @@ void run () } if (output_fixel_directory != input_fixel_directory) { - Sparse::copy_index_file (input_fixel_directory, output_fixel_directory); - Sparse::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/fod2fixel.cpp b/cmd/fod2fixel.cpp index ae4784634f..5f463d8832 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -19,8 +19,8 @@ #include "image.h" -#include "formats/fixel/keys.h" -#include "formats/fixel/helpers.h" +#include "fixel/keys.h" +#include "fixel/helpers.h" #include "math/SH.h" @@ -201,7 +201,7 @@ void Segmented_FOD_receiver::commit () std::unique_ptr disp_image (nullptr); auto index_header (H); - index_header.keyval()[Sparse::n_fixels_key] = str(n_fixels); + index_header.keyval()[Fixel::n_fixels_key] = str(n_fixels); index_header.ndim() = 4; index_header.size(3) = 2; index_header.datatype() = DataType::from(); @@ -220,7 +220,7 @@ void Segmented_FOD_receiver::commit () dir_header.size(1) = 3; dir_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_directory_path, dir_path), dir_header))); dir_image->index(1) = 0; - Sparse::check_fixel_size (*index_image, *dir_image); + Fixel::check_fixel_size (*index_image, *dir_image); } if (afd_path.size()) { @@ -228,7 +228,7 @@ void Segmented_FOD_receiver::commit () afd_header.size(1) = 1; afd_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_directory_path, afd_path), afd_header))); afd_image->index(1) = 0; - Sparse::check_fixel_size (*index_image, *afd_image); + Fixel::check_fixel_size (*index_image, *afd_image); } if (peak_path.size()) { @@ -236,7 +236,7 @@ void Segmented_FOD_receiver::commit () peak_header.size(1) = 1; peak_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_directory_path, peak_path), peak_header))); peak_image->index(1) = 0; - Sparse::check_fixel_size (*index_image, *peak_image); + Fixel::check_fixel_size (*index_image, *peak_image); } if (disp_path.size()) { @@ -244,7 +244,7 @@ void Segmented_FOD_receiver::commit () disp_header.size(1) = 1; disp_image = std::unique_ptr (new DataImage (DataImage::create (Path::join(fixel_directory_path, disp_path), disp_header))); disp_image->index(1) = 0; - Sparse::check_fixel_size (*index_image, *disp_image); + Fixel::check_fixel_size (*index_image, *disp_image); } size_t offset (0), lobe_index (0); @@ -337,7 +337,7 @@ void run () if (!receiver.num_outputs ()) throw Exception ("Nothing to do; please specify at least one output image type"); - Sparse::check_fixel_directory (fixel_directory_path, true, true); + Fixel::check_fixel_directory (fixel_directory_path, true, true); FMLS::FODQueueWriter writer (fod_data, mask); diff --git a/cmd/tck2fixel.cpp b/cmd/tck2fixel.cpp index f6d76c6246..c6aa9d6ecf 100644 --- a/cmd/tck2fixel.cpp +++ b/cmd/tck2fixel.cpp @@ -19,9 +19,9 @@ #include "algo/loop.h" #include "image.h" -#include "formats/fixel/helpers.h" -#include "formats/fixel/keys.h" -#include "formats/fixel/loop.h" +#include "fixel/helpers.h" +#include "fixel/keys.h" +#include "fixel/loop.h" #include "dwi/tractography/mapping/mapper.h" #include "dwi/tractography/mapping/loader.h" @@ -126,10 +126,10 @@ void write_fixel_output (const std::string& filename, void run () { const std::string input_fixel_folder = argument[1]; - Header index_header = Sparse::find_index_header (input_fixel_folder); + Header index_header = Fixel::find_index_header (input_fixel_folder); auto index_image = index_header.get_image(); - const uint32_t num_fixels = Sparse::get_number_of_fixels (index_header); + const uint32_t num_fixels = Fixel::get_number_of_fixels (index_header); const float angular_threshold = get_option_value ("angle", DEFAULT_ANGLE_THRESHOLD); @@ -137,10 +137,10 @@ void run () std::vector directions (num_fixels); const std::string output_fixel_folder = argument[2]; - Sparse::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 = Sparse::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) { @@ -148,7 +148,7 @@ void run () index_image.index(3) = 1; uint32_t offset = index_image.value(); size_t fixel_index = 0; - for (auto f = Sparse::FixelLoop (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; } @@ -180,7 +180,7 @@ void run () } track_file.close(); - Header output_header (Sparse::data_header_from_index (index_image)); + 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/voxel2fixel.cpp b/cmd/voxel2fixel.cpp index d6f03240fd..6436001bcc 100644 --- a/cmd/voxel2fixel.cpp +++ b/cmd/voxel2fixel.cpp @@ -19,9 +19,9 @@ #include "algo/loop.h" #include "image.h" -#include "formats/fixel/helpers.h" -#include "formats/fixel/keys.h" -#include "formats/fixel/loop.h" +#include "fixel/helpers.h" +#include "fixel/keys.h" +#include "fixel/loop.h" using namespace MR; using namespace App; @@ -47,22 +47,22 @@ void run () { auto scalar = Image::open (argument[0]); std::string input_fixel_directory = argument[1]; - Sparse::check_fixel_directory (input_fixel_directory); - auto input_fixel_index = Sparse::find_index_header (input_fixel_directory).get_image(); + 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"); progress++; - Sparse::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]), Sparse::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 = Sparse::FixelLoop (input_fixel_index) (output_fixel_data); f; ++f) + for (auto f = Fixel::Loop (input_fixel_index) (output_fixel_data); f; ++f) output_fixel_data.value() = scalar.value(); } } diff --git a/cmd/warp2metric.cpp b/cmd/warp2metric.cpp index 55c9905bc4..69e9d96aa5 100644 --- a/cmd/warp2metric.cpp +++ b/cmd/warp2metric.cpp @@ -20,9 +20,9 @@ #include "image.h" #include "adapter/jacobian.h" #include "registration/warp/helpers.h" -#include "formats/fixel/helpers.h" -#include "formats/fixel/keys.h" -#include "formats/fixel/loop.h" +#include "fixel/helpers.h" +#include "fixel/keys.h" +#include "fixel/loop.h" using namespace MR; using namespace App; @@ -79,13 +79,13 @@ void run () auto opt = get_options ("fc"); if (opt.size()) { std::string template_fixel_directory (opt[0][0]); - fixel_template_index = Sparse::find_index_header (template_fixel_directory).get_image(); - fixel_template_directions = Sparse::find_directions_header (template_fixel_directory).get_image().with_direct_io(); + 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]); if (template_fixel_directory != output_fixel_directory) { - Sparse::copy_index_file (template_fixel_directory, output_fixel_directory); - Sparse::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); } uint32_t num_fixels = 0; @@ -93,7 +93,7 @@ void run () for (auto l = Loop (fixel_template_index, 0, 3) (fixel_template_index); l; ++l) num_fixels += fixel_template_index.value(); - fc_output_data = Image::create (Path::join (output_fixel_directory, opt[0][2]), Sparse::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)); } @@ -121,7 +121,7 @@ void run () if (fc_output_data.valid()) { assign_pos_of (jacobian, 0, 3).to (fixel_template_index); - for (auto f = Sparse::FixelLoop (fixel_template_index) (fixel_template_directions, fc_output_data); f; ++f) { + 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; diff --git a/lib/formats/fixel/helpers.h b/lib/fixel/helpers.h similarity index 95% rename from lib/formats/fixel/helpers.h rename to lib/fixel/helpers.h index 7e6f67bf8c..1abc6efd7c 100644 --- a/lib/formats/fixel/helpers.h +++ b/lib/fixel/helpers.h @@ -13,11 +13,11 @@ * */ -#ifndef __formats_fixel_helpers_h__ -#define __formats_fixel_helpers_h__ +#ifndef __fixel_helpers_h__ +#define __fixel_helpers_h__ #include "formats/mrtrix_utils.h" -#include "formats/fixel/keys.h" +#include "fixel/keys.h" #include "algo/loop.h" #include "image_diff.h" @@ -31,7 +31,7 @@ namespace MR : Exception(previous_exception, msg) {} }; - namespace Sparse + namespace Fixel { FORCE_INLINE bool is_index_image (const Header& in) { @@ -239,7 +239,7 @@ namespace MR bool directions_found (false); Header header; check_fixel_directory (fixel_directory_path); - Header index_header = Sparse::find_index_header (fixel_directory_path); + Header index_header = Fixel::find_index_header (fixel_directory_path); auto dir_walker = Path::Dir (fixel_directory_path); std::string fname; @@ -300,7 +300,7 @@ namespace MR //! Copy the index file from one fixel directory into another FORCE_INLINE void copy_index_file (const std::string& input_directory, const std::string& output_directory) { - Header input_header = Sparse::find_index_header (input_directory); + Header input_header = Fixel::find_index_header (input_directory); check_fixel_directory (output_directory, true); std::string output_path = Path::join (output_directory, Path::basename (input_header.name())); @@ -323,7 +323,7 @@ namespace MR //! Copy the directions file from one fixel directory into another. FORCE_INLINE void copy_directions_file (const std::string& input_directory, const std::string& output_directory) { - Header input_header = Sparse::find_directions_header (input_directory); + Header input_header = Fixel::find_directions_header (input_directory); std::string output_path = Path::join (output_directory, Path::basename (input_header.name())); // If the index file already exists check it is the same as the input index file @@ -349,7 +349,7 @@ namespace MR //! Copy all data files in a fixel directory into another directory. Data files do not include the index or directions file. FORCE_INLINE void copy_all_data_files (const std::string &input_directory, const std::string &output_directory) { - for (auto& input_header : Sparse::find_data_headers (input_directory, Sparse::find_index_header (input_directory))) + for (auto& input_header : Fixel::find_data_headers (input_directory, Fixel::find_index_header (input_directory))) copy_fixel_file (input_header.name(), output_directory); } @@ -360,17 +360,15 @@ namespace MR throw Exception ("please input the specific fixel data file to be converted (not the fixel directory)"); Header in_data_header = Header::open (input_file); - Sparse::check_data_file (in_data_header); + Fixel::check_data_file (in_data_header); auto in_data_image = in_data_header.get_image(); - Header in_index_header = Sparse::find_index_header (Sparse::get_fixel_directory (input_file)); + Header in_index_header = Fixel::find_index_header (Fixel::get_fixel_directory (input_file)); if (input_file == in_index_header.name()) throw Exception ("input fixel data file cannot be the index file"); return in_data_image; } - - } } diff --git a/lib/formats/fixel/keys.h b/lib/fixel/keys.h similarity index 89% rename from lib/formats/fixel/keys.h rename to lib/fixel/keys.h index 58ddf0e613..2c585acbab 100644 --- a/lib/formats/fixel/keys.h +++ b/lib/fixel/keys.h @@ -13,14 +13,14 @@ * */ -#ifndef __formats_fixel_keys_h__ -#define __formats_fixel_keys_h__ +#ifndef __fixel_keys_h__ +#define __fixel_keys_h__ #include namespace MR { - namespace Sparse + namespace Fixel { const std::string n_fixels_key ("nfixels"); const std::initializer_list supported_sparse_formats { ".mif", ".nii", ".mif.gz" , ".nii.gz" }; diff --git a/lib/formats/fixel/legacy/fixel_metric.h b/lib/fixel/legacy/fixel_metric.h similarity index 93% rename from lib/formats/fixel/legacy/fixel_metric.h rename to lib/fixel/legacy/fixel_metric.h index 903edfa447..aec39b4c82 100644 --- a/lib/formats/fixel/legacy/fixel_metric.h +++ b/lib/fixel/legacy/fixel_metric.h @@ -13,15 +13,16 @@ * */ -#ifndef __formats_fixel_legacy_metric_h__ -#define __formats_fixel_legacy_metric_h__ +#ifndef __fixel_legacy_metric_h__ +#define __fixel_legacy_metric_h__ #include "types.h" namespace MR { - namespace Sparse + namespace Fixel { + namespace Legacy { @@ -48,7 +49,6 @@ namespace MR float value; }; - } } } diff --git a/lib/formats/fixel/legacy/image.h b/lib/fixel/legacy/image.h similarity index 93% rename from lib/formats/fixel/legacy/image.h rename to lib/fixel/legacy/image.h index 339773f54c..882b8f9783 100644 --- a/lib/formats/fixel/legacy/image.h +++ b/lib/fixel/legacy/image.h @@ -13,29 +13,28 @@ * */ -#ifndef __formats_fixel_legacy_image_h__ -#define __formats_fixel_legacy_image_h__ +#ifndef __fixel_legacy_image_h__ +#define __fixel_legacy_image_h__ #include #include "image.h" #include "header.h" #include "image_io/sparse.h" -#include "formats/fixel/legacy/keys.h" +#include "fixel/legacy/keys.h" #ifndef __image_h__ -#error File that #includes "formats/fixel/legacy/image.h" must explicitly #include "image.h" beforehand +#error File that #includes "fixel/legacy/image.h" must explicitly #include "image.h" beforehand #endif namespace MR { - namespace Sparse - { + namespace Fixel + { namespace Legacy { - template class Value { public: @@ -126,14 +125,14 @@ namespace MR if (typeid (*ptr) != typeid (ImageIO::SparseLegacy)) throw Exception ("cannot create sparse image to access non-sparse data"); // Use the header information rather than trying to access this from the handler - std::map::const_iterator name_it = keyval().find (Sparse::Legacy::name_key); + std::map::const_iterator name_it = keyval().find (Fixel::Legacy::name_key); if (name_it == keyval().end()) throw Exception ("cannot create sparse image without knowledge of underlying class type in the image header"); // TODO temporarily disabled this to allow updated_syntax tests to pass with files generated with master branch. // const std::string& class_name = name_it->second; // if (str(typeid(DataType).name()) != class_name) // throw Exception ("class type of sparse image buffer (" + str(typeid(DataType).name()) + ") does not match that in image header (" + class_name + ")"); - std::map::const_iterator size_it = keyval().find (Sparse::Legacy::size_key); + std::map::const_iterator size_it = keyval().find (Fixel::Legacy::size_key); if (size_it == keyval().end()) throw Exception ("cannot create sparse image without knowledge of underlying class size in the image header"); const size_t class_size = to(size_it->second); @@ -146,7 +145,6 @@ namespace MR }; - } } } diff --git a/lib/formats/fixel/legacy/keys.h b/lib/fixel/legacy/keys.h similarity index 89% rename from lib/formats/fixel/legacy/keys.h rename to lib/fixel/legacy/keys.h index 36944acde2..4feda5c194 100644 --- a/lib/formats/fixel/legacy/keys.h +++ b/lib/fixel/legacy/keys.h @@ -13,18 +13,18 @@ * */ -#ifndef __formats_fixel_legacy_keys_h__ -#define __formats_fixel_legacy_keys_h__ +#ifndef __fixel_legacy_keys_h__ +#define __fixel_legacy_keys_h__ #include namespace MR { - namespace Sparse + namespace Fixel { - namespace Legacy { + // These are the keys that must be present in an image header to successfully read or write sparse image data const std::string name_key ("sparse_data_name"); const std::string size_key ("sparse_data_size"); diff --git a/lib/formats/fixel/loop.h b/lib/fixel/loop.h similarity index 94% rename from lib/formats/fixel/loop.h rename to lib/fixel/loop.h index 03a4a57e35..8aa0c97334 100644 --- a/lib/formats/fixel/loop.h +++ b/lib/fixel/loop.h @@ -13,14 +13,14 @@ * */ -#ifndef __formats_fixel_loop_h__ -#define __formats_fixel_loop_h__ +#ifndef __fixel_loop_h__ +#define __fixel_loop_h__ #include "formats/mrtrix_utils.h" namespace MR { - namespace Sparse { + namespace Fixel { namespace { @@ -63,14 +63,13 @@ namespace MR template FORCE_INLINE LoopFixelsInVoxel - FixelLoop (IndexType& index) { + Loop (IndexType& index) { index.index(3) = 0; size_t num_fixels = index.value(); index.index(3) = 1; uint32_t offset = index.value(); return { num_fixels, offset }; } - } } diff --git a/lib/formats/mrtrix_sparse_legacy.cpp b/lib/formats/mrtrix_sparse_legacy.cpp index 0642cefab9..5c938875cc 100644 --- a/lib/formats/mrtrix_sparse_legacy.cpp +++ b/lib/formats/mrtrix_sparse_legacy.cpp @@ -26,7 +26,7 @@ #include "image_io/sparse.h" #include "formats/list.h" #include "formats/mrtrix_utils.h" -#include "formats/fixel/legacy/keys.h" +#include "fixel/legacy/keys.h" @@ -62,11 +62,11 @@ namespace MR if (H.datatype() != dt) throw Exception ("Cannot open sparse image file " + H.name() + " due to type mismatch; expect " + dt.description() + ", file is " + H.datatype().description()); - const auto name_it = H.keyval().find (Sparse::Legacy::name_key); + const auto name_it = H.keyval().find (Fixel::Legacy::name_key); if (name_it == H.keyval().end()) throw Exception ("sparse data class name not specified in sparse image header " + H.name()); - const auto size_it = H.keyval().find (Sparse::Legacy::size_key); + const auto size_it = H.keyval().find (Fixel::Legacy::size_key); if (size_it == H.keyval().end()) throw Exception ("sparse data class size not specified in sparse image header " + H.name()); @@ -97,8 +97,8 @@ namespace MR !Path::has_suffix (H.name(), ".msf")) return false; - if (H.keyval().find (Sparse::Legacy::name_key) == H.keyval().end() || - H.keyval().find (Sparse::Legacy::size_key) == H.keyval().end()) + if (H.keyval().find (Fixel::Legacy::name_key) == H.keyval().end() || + H.keyval().find (Fixel::Legacy::size_key) == H.keyval().end()) return false; H.ndim() = num_axes; @@ -117,11 +117,11 @@ namespace MR std::unique_ptr MRtrix_sparse::create (Header& H) const { - const auto name_it = H.keyval().find (Sparse::Legacy::name_key); + const auto name_it = H.keyval().find (Fixel::Legacy::name_key); if (name_it == H.keyval().end()) throw Exception ("Cannot create sparse image " + H.name() + "; no knowledge of underlying data class type"); - const auto size_it = H.keyval().find (Sparse::Legacy::size_key); + const auto size_it = H.keyval().find (Fixel::Legacy::size_key); if (size_it == H.keyval().end()) throw Exception ("Cannot create sparse image " + H.name() + "; no knowledge of underlying data class size"); diff --git a/lib/image_io/sparse.cpp b/lib/image_io/sparse.cpp index 066c60de43..d6e0f77196 100644 --- a/lib/image_io/sparse.cpp +++ b/lib/image_io/sparse.cpp @@ -58,7 +58,7 @@ namespace MR // Writes a single uint32_t(0) to the start of the sparse data region // Any voxel that has its value initialised to 0 will point here, and therefore dereferencing of any - // such voxel will yield a Sparse::Value with zero elements + // such voxel will yield a Fixel::Value with zero elements memset (off2mem (0), 0x00, sizeof (uint32_t)); data_end = sizeof(uint32_t); diff --git a/lib/image_io/sparse.h b/lib/image_io/sparse.h index 34e25f5205..bd0dff709b 100644 --- a/lib/image_io/sparse.h +++ b/lib/image_io/sparse.h @@ -41,7 +41,7 @@ namespace MR // A quick description of how the sparse image data are currently stored: // * The data are either after the image data within the same file if extension is .msf, or // in a separate file with the .sdat extension if the image extension if .msh - // * The image header must store the fields defined in lib/image/formats/fixel/key.h + // * The image header must store the fields defined in lib/image/fixel/key.h // These are currently verified on construction of the BufferSparse class. This proved to // be simpler than trying to verify class matching on every interaction with the handler // using templated functions. diff --git a/src/dwi/tractography/SIFT/output.h b/src/dwi/tractography/SIFT/output.h index 7aaf92f66b..e9322788d0 100644 --- a/src/dwi/tractography/SIFT/output.h +++ b/src/dwi/tractography/SIFT/output.h @@ -31,9 +31,9 @@ #include "math/SH.h" -#include "formats/fixel/legacy/fixel_metric.h" -#include "formats/fixel/legacy/image.h" -#include "formats/fixel/legacy/keys.h" +#include "fixel/legacy/fixel_metric.h" +#include "fixel/legacy/image.h" +#include "fixel/legacy/keys.h" namespace MR @@ -98,13 +98,13 @@ namespace MR template void ModelBase::output_target_image_fixel (const std::string& path) const { - using Sparse::Legacy::FixelMetric; + using MR::Fixel::Legacy::FixelMetric; Header H_fixel (Fixel_map::header()); H_fixel.datatype() = DataType::UInt64; H_fixel.datatype().set_byte_order_native(); - H_fixel.keyval()[Sparse::Legacy::name_key] = str(typeid(FixelMetric).name()); - H_fixel.keyval()[Sparse::Legacy::size_key] = str(sizeof(FixelMetric)); - Sparse::Legacy::Image out (path, H_fixel); + H_fixel.keyval()[MR::Fixel::Legacy::name_key] = str(typeid(FixelMetric).name()); + H_fixel.keyval()[MR::Fixel::Legacy::size_key] = str(sizeof(FixelMetric)); + MR::Fixel::Legacy::Image out (path, H_fixel); VoxelAccessor v (accessor()); for (auto l = Loop (out) (out, v); l; ++l) { if (v.value()) { @@ -192,14 +192,14 @@ namespace MR template void ModelBase::output_tdi_fixel (const std::string& path) const { - using Sparse::Legacy::FixelMetric; + using MR::Fixel::Legacy::FixelMetric; const default_type current_mu = mu(); Header H_fixel (Fixel_map::header()); H_fixel.datatype() = DataType::UInt64; H_fixel.datatype().set_byte_order_native(); - H_fixel.keyval()[Sparse::Legacy::name_key] = str(typeid(FixelMetric).name()); - H_fixel.keyval()[Sparse::Legacy::size_key] = str(sizeof(FixelMetric)); - Sparse::Legacy::Image out (path, H_fixel); + H_fixel.keyval()[MR::Fixel::Legacy::name_key] = str(typeid(FixelMetric).name()); + H_fixel.keyval()[MR::Fixel::Legacy::size_key] = str(sizeof(FixelMetric)); + MR::Fixel::Legacy::Image out (path, H_fixel); VoxelAccessor v (accessor()); for (auto l = Loop (out) (out, v); l; ++l) { if (v.value()) { @@ -244,15 +244,15 @@ namespace MR template void ModelBase::output_error_fixel_images (const std::string& diff_path, const std::string& cost_path) const { - using Sparse::Legacy::FixelMetric; + using MR::Fixel::Legacy::FixelMetric; const default_type current_mu = mu(); Header H_fixel (Fixel_map::header()); H_fixel.datatype() = DataType::UInt64; H_fixel.datatype().set_byte_order_native(); - H_fixel.keyval()[Sparse::Legacy::name_key] = str(typeid(FixelMetric).name()); - H_fixel.keyval()[Sparse::Legacy::size_key] = str(sizeof(FixelMetric)); - Sparse::Legacy::Image out_diff (diff_path, H_fixel); - Sparse::Legacy::Image out_cost (cost_path, H_fixel); + H_fixel.keyval()[MR::Fixel::Legacy::name_key] = str(typeid(FixelMetric).name()); + H_fixel.keyval()[MR::Fixel::Legacy::size_key] = str(sizeof(FixelMetric)); + MR::Fixel::Legacy::Image out_diff (diff_path, H_fixel); + MR::Fixel::Legacy::Image out_cost (cost_path, H_fixel); VoxelAccessor v (accessor()); for (auto l = Loop (v) (v, out_diff, out_cost); l; ++l) { if (v.value()) { diff --git a/src/dwi/tractography/SIFT/sifter.cpp b/src/dwi/tractography/SIFT/sifter.cpp index d9e9d6261f..4e2c67a005 100644 --- a/src/dwi/tractography/SIFT/sifter.cpp +++ b/src/dwi/tractography/SIFT/sifter.cpp @@ -34,7 +34,7 @@ #include "math/rng.h" -#include "formats/fixel/legacy/image.h" +#include "fixel/legacy/image.h" diff --git a/src/dwi/tractography/SIFT2/tckfactor.cpp b/src/dwi/tractography/SIFT2/tckfactor.cpp index 78c9cbde78..820a462898 100644 --- a/src/dwi/tractography/SIFT2/tckfactor.cpp +++ b/src/dwi/tractography/SIFT2/tckfactor.cpp @@ -20,9 +20,9 @@ #include "math/math.h" -#include "formats/fixel/legacy/fixel_metric.h" -#include "formats/fixel/legacy/image.h" -#include "formats/fixel/legacy/keys.h" +#include "fixel/legacy/fixel_metric.h" +#include "fixel/legacy/image.h" +#include "fixel/legacy/keys.h" #include "dwi/tractography/SIFT2/coeff_optimiser.h" #include "dwi/tractography/SIFT2/fixel_updater.h" @@ -384,20 +384,20 @@ namespace MR { maxs[i] = 0.0; } - using Sparse::Legacy::FixelMetric; + using MR::Fixel::Legacy::FixelMetric; Header H_fixel (Fixel_map::header()); H_fixel.datatype() = DataType::UInt64; H_fixel.datatype().set_byte_order_native(); - H_fixel.keyval()[Sparse::Legacy::name_key] = str(typeid(FixelMetric).name()); - H_fixel.keyval()[Sparse::Legacy::size_key] = str(sizeof(FixelMetric)); - - Sparse::Legacy::Image count_image (prefix + "_count.msf", H_fixel); - Sparse::Legacy::Image min_image (prefix + "_coeff_min.msf", H_fixel); - Sparse::Legacy::Image mean_image (prefix + "_coeff_mean.msf", H_fixel); - Sparse::Legacy::Image stdev_image (prefix + "_coeff_stdev.msf", H_fixel); - Sparse::Legacy::Image max_image (prefix + "_coeff_max.msf", H_fixel); - Sparse::Legacy::Image zeroed_image (prefix + "_coeff_zeroed.msf", H_fixel); - Sparse::Legacy::Image excluded_image (prefix + "_excluded.msf", H_fixel); + H_fixel.keyval()[MR::Fixel::Legacy::name_key] = str(typeid(FixelMetric).name()); + H_fixel.keyval()[MR::Fixel::Legacy::size_key] = str(sizeof(FixelMetric)); + + MR::Fixel::Legacy::Image count_image (prefix + "_count.msf", H_fixel); + MR::Fixel::Legacy::Image min_image (prefix + "_coeff_min.msf", H_fixel); + MR::Fixel::Legacy::Image mean_image (prefix + "_coeff_mean.msf", H_fixel); + MR::Fixel::Legacy::Image stdev_image (prefix + "_coeff_stdev.msf", H_fixel); + MR::Fixel::Legacy::Image max_image (prefix + "_coeff_max.msf", H_fixel); + MR::Fixel::Legacy::Image zeroed_image (prefix + "_coeff_zeroed.msf", H_fixel); + MR::Fixel::Legacy::Image excluded_image (prefix + "_excluded.msf", H_fixel); VoxelAccessor v (accessor()); for (auto l = Loop(v) (v, count_image, min_image, mean_image, stdev_image, max_image, zeroed_image, excluded_image); l; ++l) { diff --git a/src/dwi/tractography/seeding/dynamic.cpp b/src/dwi/tractography/seeding/dynamic.cpp index cbb54c7cb0..33ae1b865e 100644 --- a/src/dwi/tractography/seeding/dynamic.cpp +++ b/src/dwi/tractography/seeding/dynamic.cpp @@ -21,9 +21,9 @@ #include "dwi/tractography/rng.h" #include "dwi/tractography/seeding/dynamic.h" -#include "formats/fixel/legacy/fixel_metric.h" -#include "formats/fixel/legacy/keys.h" -#include "formats/fixel/legacy/image.h" +#include "fixel/legacy/fixel_metric.h" +#include "fixel/legacy/keys.h" +#include "fixel/legacy/image.h" @@ -136,9 +136,9 @@ namespace MR H.info() = info(); H.datatype() = DataType::UInt64; H.datatype().set_byte_order_native(); - H[Image::Sparse::Legacy::name_key] = str(typeid(Image::Sparse::Legacy::FixelMetric).name()); - H[Image::Sparse::Legacy::size_key] = str(sizeof(Image::Sparse::Legacy::FixelMetric)); - Image::BufferSparse buffer_probs ("final_seed_probs.msf", H), buffer_logprobs ("final_seed_logprobs.msf", H), buffer_ratios ("final_fixel_ratios.msf", H); + H[Image::Fixel::Legacy::name_key] = str(typeid(Image::Fixel::Legacy::FixelMetric).name()); + H[Image::Fixel::Legacy::size_key] = str(sizeof(Image::Fixel::Legacy::FixelMetric)); + Image::BufferSparse buffer_probs ("final_seed_probs.msf", H), buffer_logprobs ("final_seed_logprobs.msf", H), buffer_ratios ("final_fixel_ratios.msf", H); auto out_probs = buffer_probs.voxel(), out_logprobs = buffer_logprobs.voxel(), out_ratios = buffer_ratios.voxel(); VoxelAccessor v (accessor); Image::Loop loop; @@ -149,7 +149,7 @@ namespace MR out_ratios .value().set_size ((*v.value()).num_fixels()); size_t index = 0; for (Fixel_map::ConstIterator i = begin (v); i; ++i, ++index) { - Image::Sparse::Legacy::FixelMetric fixel (i().get_dir(), i().get_FOD(), i().get_old_prob()); + Image::Fixel::Legacy::FixelMetric fixel (i().get_dir(), i().get_FOD(), i().get_old_prob()); out_probs.value()[index] = fixel; fixel.value = log10 (fixel.value); out_logprobs.value()[index] = fixel; @@ -286,9 +286,9 @@ namespace MR H.info() = info(); H.datatype() = DataType::UInt64; H.datatype().set_byte_order_native(); - H[Image::Sparse::Legacy::name_key] = str(typeid(Image::Sparse::Legacy::FixelMetric).name()); - H[Image::Sparse::Legacy::size_key] = str(sizeof(Image::Sparse::Legacy::FixelMetric)); - Image::BufferSparse buffer_probs ("mid_seed_probs.msf", H), buffer_logprobs ("mid_seed_logprobs.msf", H), buffer_ratios ("mid_fixel_ratios.msf", H), buffer_FDs ("mid_FDs.msf", H), buffer_TDs ("mid_TDs.msf", H); + H[Image::Fixel::Legacy::name_key] = str(typeid(Image::Fixel::Legacy::FixelMetric).name()); + H[Image::Fixel::Legacy::size_key] = str(sizeof(Image::Fixel::Legacy::FixelMetric)); + Image::BufferSparse buffer_probs ("mid_seed_probs.msf", H), buffer_logprobs ("mid_seed_logprobs.msf", H), buffer_ratios ("mid_fixel_ratios.msf", H), buffer_FDs ("mid_FDs.msf", H), buffer_TDs ("mid_TDs.msf", H); auto out_probs = buffer_probs.voxel(), out_logprobs = buffer_logprobs.voxel(), out_ratios = buffer_ratios.voxel(), out_FDs = buffer_FDs.voxel(), out_TDs = buffer_TDs.voxel(); VoxelAccessor v (accessor); Image::Loop loop; @@ -301,7 +301,7 @@ namespace MR out_TDs .value().set_size ((*v.value()).num_fixels()); size_t index = 0; for (Fixel_map::ConstIterator i = begin (v); i; ++i, ++index) { - Image::Sparse::Legacy::FixelMetric fixel (i().get_dir(), i().get_FOD(), i().get_old_prob()); + Image::Fixel::Legacy::FixelMetric fixel (i().get_dir(), i().get_FOD(), i().get_old_prob()); out_probs.value()[index] = fixel; fixel.value = log10 (fixel.value); out_logprobs.value()[index] = fixel; @@ -341,9 +341,9 @@ namespace MR H.info() = info(); H.datatype() = DataType::UInt64; H.datatype().set_byte_order_native(); - H[Image::Sparse::Legacy::name_key] = str(typeid(Image::Sparse::Legacy::FixelMetric).name()); - H[Image::Sparse::Legacy::size_key] = str(sizeof(Image::Sparse::Legacy::FixelMetric)); - Image::BufferSparse buffer ("fixel_mask.msf", H); + H[Image::Fixel::Legacy::name_key] = str(typeid(Image::Fixel::Legacy::FixelMetric).name()); + H[Image::Fixel::Legacy::size_key] = str(sizeof(Image::Fixel::Legacy::FixelMetric)); + Image::BufferSparse buffer ("fixel_mask.msf", H); auto out = buffer.voxel(); VoxelAccessor v (accessor); Image::Loop loop; @@ -352,7 +352,7 @@ namespace MR out.value().set_size ((*v.value()).num_fixels()); size_t index = 0; for (Fixel_map::ConstIterator f = begin(v); f; ++f, ++index) { - Image::Sparse::Legacy::FixelMetric fixel (f().get_dir(), f().get_FOD(), (f().can_update() ? 1.0f : 0.0f)); + Image::Fixel::Legacy::FixelMetric fixel (f().get_dir(), f().get_FOD(), (f().can_update() ? 1.0f : 0.0f)); out.value()[index] = fixel; } } diff --git a/src/gui/mrview/tool/vector/fixel.h b/src/gui/mrview/tool/vector/fixel.h index 029a46de8c..f47ffcec84 100644 --- a/src/gui/mrview/tool/vector/fixel.h +++ b/src/gui/mrview/tool/vector/fixel.h @@ -22,9 +22,9 @@ #include "transform.h" #include "algo/loop.h" -#include "formats/fixel/legacy/image.h" -#include "formats/fixel/legacy/fixel_metric.h" -#include "formats/fixel/helpers.h" +#include "fixel/legacy/image.h" +#include "fixel/legacy/fixel_metric.h" +#include "fixel/helpers.h" #include "gui/mrview/displayable.h" #include "gui/mrview/tool/vector/vector.h" @@ -319,7 +319,7 @@ namespace MR } }; - typedef MR::Sparse::Legacy::Image FixelSparseImageType; + typedef MR::Fixel::Legacy::Image FixelSparseImageType; typedef MR::Image FixelPackedImageType; typedef MR::Image FixelIndexImageType; } diff --git a/src/gui/mrview/tool/vector/fixelfolder.cpp b/src/gui/mrview/tool/vector/fixelfolder.cpp index a63b3dbbe7..7b2b0e63b5 100644 --- a/src/gui/mrview/tool/vector/fixelfolder.cpp +++ b/src/gui/mrview/tool/vector/fixelfolder.cpp @@ -60,7 +60,7 @@ namespace MR // Load fixel direction images - auto directions_image = Sparse::find_directions_header (Path::dirname (fixel_data->name())).get_image().with_direct_io (); + auto directions_image = Fixel::find_directions_header (Path::dirname (fixel_data->name())).get_image().with_direct_io (); directions_image.index (1) = 0; for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { fixel_data->index (3) = 0; @@ -75,7 +75,7 @@ namespace MR // Load fixel data images keys // We will load the actual fixel data lazily upon request - auto data_headers = Sparse::find_data_headers (Path::dirname (fixel_data->name ()), *fixel_data); + auto data_headers = Fixel::find_data_headers (Path::dirname (fixel_data->name ()), *fixel_data); for (auto& header : data_headers) { if (header.size (1) != 1) continue; @@ -99,7 +99,7 @@ namespace MR auto H = Header::open (data_filepath); - if (!Sparse::is_data_file (H)) + if (!Fixel::is_data_file (H)) return; auto data_image = H.get_image (); diff --git a/src/gui/mrview/tool/vector/fixelfolder.h b/src/gui/mrview/tool/vector/fixelfolder.h index 224389e475..f50ac3ac37 100644 --- a/src/gui/mrview/tool/vector/fixelfolder.h +++ b/src/gui/mrview/tool/vector/fixelfolder.h @@ -29,7 +29,7 @@ namespace MR { public: FixelFolder (const std::string& filename, Vector& fixel_tool) : - FixelType (Sparse::find_index_header (Path::dirname (filename)).name (), fixel_tool) + FixelType (Fixel::find_index_header (Path::dirname (filename)).name (), fixel_tool) { value_types = {"unity"}; colour_types = {"direction"}; diff --git a/testing/cmd/testing_diff_fixel.cpp b/testing/cmd/testing_diff_fixel.cpp index e61c4ac360..397958a633 100644 --- a/testing/cmd/testing_diff_fixel.cpp +++ b/testing/cmd/testing_diff_fixel.cpp @@ -18,7 +18,7 @@ #include "image.h" -#include "formats/fixel/helpers.h" +#include "fixel/helpers.h" #include "diff_images.h" @@ -45,9 +45,9 @@ void usage () void run () { std::string fixel_directory1 = argument[0]; - Sparse::check_fixel_directory (fixel_directory1); + Fixel::check_fixel_directory (fixel_directory1); std::string fixel_directory2 = argument[1]; - Sparse::check_fixel_directory (fixel_directory2); + Fixel::check_fixel_directory (fixel_directory2); if (fixel_directory1 == fixel_directory2) throw Exception ("Input fixel directorys are the same"); diff --git a/testing/cmd/testing_diff_fixel_old.cpp b/testing/cmd/testing_diff_fixel_old.cpp index 22106c57cb..e9b163c73b 100644 --- a/testing/cmd/testing_diff_fixel_old.cpp +++ b/testing/cmd/testing_diff_fixel_old.cpp @@ -21,11 +21,11 @@ #include "image.h" -#include "formats/fixel/image.h" -#include "formats/fixel/fixel_metric.h" +#include "fixel/legacy/image.h" +#include "fixel/legacy/fixel_metric.h" #include "image_helpers.h" #include "algo/threaded_loop.h" -using MR::Sparse::Legacy::FixelMetric; +using MR::Fixel::Legacy::FixelMetric; using namespace MR; using namespace App; @@ -46,8 +46,8 @@ void usage () void run () { - Sparse::Legacy::Image buffer1 (argument[0]); - Sparse::Legacy::Image buffer2 (argument[1]); + Fixel::Legacy::Image buffer1 (argument[0]); + Fixel::Legacy::Image buffer2 (argument[1]); check_dimensions (buffer1, buffer2); for (size_t i = 0; i < buffer1.ndim(); ++i) { if (std::isfinite (buffer1.spacing(i))) From 1b7c96f6d99d493d17cfe433130a805ea9f4e3d0 Mon Sep 17 00:00:00 2001 From: Max Pietsch Date: Fri, 18 Nov 2016 10:21:25 +0000 Subject: [PATCH 236/723] smooth filter: 1D in place smoothing kernel --- lib/adapter/gaussian1D_buffered.h | 139 ------------------------------ lib/filter/smooth.h | 105 +++++++++++++++++++++- 2 files changed, 102 insertions(+), 142 deletions(-) delete mode 100644 lib/adapter/gaussian1D_buffered.h diff --git a/lib/adapter/gaussian1D_buffered.h b/lib/adapter/gaussian1D_buffered.h deleted file mode 100644 index fcc98f5fb2..0000000000 --- a/lib/adapter/gaussian1D_buffered.h +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2008-2016 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/ - * - * MRtrix is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * For more details, see www.mrtrix.org - * - */ - -#ifndef __image_adapter_gaussian1D_buffered_h__ -#define __image_adapter_gaussian1D_buffered_h__ - -#include "adapter/base.h" - -namespace MR -{ - namespace Adapter - { - - template - class Gaussian1DBuffered : public Base { - public: - Gaussian1DBuffered (const ImageType& parent, - default_type stdev_in = 1.0, - size_t axis_in = 0, - size_t extent = 0, - bool zero_boundary = false) : - Base (parent), - stdev (stdev_in), - axis (axis_in), - zero_boundary (zero_boundary), - pos_prev (std::numeric_limits::max()) { - if (!extent) - radius = ceil(2 * stdev / spacing(axis)); - else if (extent == 1) - radius = 0; - else - radius = (extent - 1) / 2; - compute_kernel(); - buffer_size = size(axis); - buffer.resize(buffer_size); - } - - typedef typename ImageType::value_type value_type; - - value_type value () - { - if (!kernel.size()) - return Base::value(); - - const ssize_t pos = index (axis); - assert (pos_prev != pos && "Loop axis has to be equal to smoothing axis"); - - // fill buffer for current image line if necessary - if (pos == 0) { - assert (pos == 0); - for (ssize_t k = 0; k < buffer_size; ++k) { - index(axis) = k; - buffer(k) = Base::value(); - } - index (axis) = pos; - } else { - assert (pos_prev + 1 == pos && "Loop not contiguous along smoothing axis"); - } - - if (zero_boundary) - if (pos == 0 || pos == size(axis) - 1) - return 0.0; - - const ssize_t from = (pos < radius) ? 0 : pos - radius; - const ssize_t to = (pos + radius) >= size(axis) ? size(axis) - 1 : pos + radius; - - ssize_t c = (pos < radius) ? radius - pos : 0; - ssize_t kernel_size = to - from + 1; - - value_type result = kernel.segment(c, kernel_size).dot(buffer.segment(from, kernel_size)); - - if (!std::isfinite(result)) { - result = 0.0; - value_type av_weights = 0.0; - for (ssize_t k = from; k <= to; ++k, ++c) { - value_type neighbour_value = buffer(k); - if (std::isfinite (neighbour_value)) { - av_weights += kernel[c]; - result += neighbour_value * kernel[c]; - } - } - result /= av_weights; - } else if (kernel_size != kernel.size()) - result /= kernel.segment(c, kernel_size).sum(); - - pos_prev = pos; - - return result; - } - - using Base::name; - using Base::size; - using Base::spacing; - using Base::index; - - protected: - - void compute_kernel() - { - if ((radius < 1) || stdev <= 0.0) - return; - kernel.resize (2 * radius + 1); - default_type norm_factor = 0.0; - for (size_t c = 0; c < kernel.size(); ++c) { - kernel[c] = exp(-((c-radius) * (c-radius) * spacing(axis) * spacing(axis)) / (2 * stdev * stdev)); - norm_factor += kernel[c]; - } - for (size_t c = 0; c < kernel.size(); c++) { - kernel[c] /= norm_factor; - } - } - - default_type stdev; - ssize_t radius; - size_t axis; - Eigen::VectorXd kernel; - const bool zero_boundary; - ssize_t pos_prev; - ssize_t buffer_size; - Eigen::VectorXd buffer; - }; - } -} - - -#endif - diff --git a/lib/filter/smooth.h b/lib/filter/smooth.h index 3193989d1e..d7ed145ad9 100644 --- a/lib/filter/smooth.h +++ b/lib/filter/smooth.h @@ -21,7 +21,6 @@ #include "algo/copy.h" #include "algo/threaded_copy.h" #include "adapter/gaussian1D.h" -#include "adapter/gaussian1D_buffered.h" #include "filter/base.h" namespace MR @@ -43,6 +42,7 @@ namespace MR * * \endcode */ + class Smooth : public Base { @@ -165,7 +165,6 @@ namespace MR for (size_t dim = 0; dim < 3; dim++) { if (stdev[dim] > 0) { - Adapter::Gaussian1DBuffered gaussian (in_and_output, stdev[dim], dim, extent[dim], zero_boundary); std::vector axes (in_and_output.ndim(), dim); size_t axdim = 1; for (size_t i = 0; i < in_and_output.ndim(); ++i) { @@ -174,7 +173,8 @@ namespace MR axes[axdim++] = stride_order[i]; } DEBUG ("smoothing dimension " + str(dim) + " in place with stride order: " + str(axes)); - threaded_copy (gaussian, in_and_output, axes, 1); + SmoothFunctor1D smooth (in_and_output, stdev[dim], dim, extent[dim], zero_boundary); + ThreadedLoop (in_and_output, axes, 1).run (smooth, in_and_output); if (progress) ++(*progress); } @@ -186,6 +186,105 @@ namespace MR std::vector stdev; const std::vector stride_order; bool zero_boundary; + + template + class SmoothFunctor1D { + public: + SmoothFunctor1D (ImageType& image, + default_type stdev_in = 1.0, + size_t axis_in = 0, + size_t extent = 0, + bool zero_boundary_in = false): + stdev (stdev_in), + axis (axis_in), + zero_boundary (zero_boundary_in), + spacing (image.spacing(axis_in)), + buffer_size (image.size(axis_in)) { + buffer.resize(buffer_size); + if (!extent) + radius = ceil(2 * stdev / spacing); + else if (extent == 1) + radius = 0; + else + radius = (extent - 1) / 2; + compute_kernel(); + } + + typedef typename ImageType::value_type value_type; + + void compute_kernel() { + if ((radius < 1) || stdev <= 0.0) + return; + kernel.resize (2 * radius + 1); + default_type norm_factor = 0.0; + for (size_t c = 0; c < kernel.size(); ++c) { + kernel[c] = exp(-((c-radius) * (c-radius) * spacing * spacing) / (2 * stdev * stdev)); + norm_factor += kernel[c]; + } + for (size_t c = 0; c < kernel.size(); c++) { + kernel[c] /= norm_factor; + } + } + + // SmoothFunctor1D operator(): + // the inner loop axis has to be the dimension the smoothing is applied to and + // the loop has to start with image.index (smooth_axis) == 0 + void operator () (ImageType& image) { + if (!kernel.size()) + return; + + const ssize_t pos = image.index (axis); + + // fill buffer for current image line if necessary + if (pos == 0) { + for (ssize_t k = 0; k < buffer_size; ++k) { + image.index(axis) = k; + buffer(k) = image.value(); + } + image.index (axis) = pos; + } + + if (zero_boundary) + if (pos == 0 || pos == image.size(axis) - 1) { + image.value() = 0.0; + return; + } + + const ssize_t from = (pos < radius) ? 0 : pos - radius; + const ssize_t to = (pos + radius) >= image.size(axis) ? image.size(axis) - 1 : pos + radius; + + ssize_t c = (pos < radius) ? radius - pos : 0; + ssize_t kernel_size = to - from + 1; + + value_type result = kernel.segment(c, kernel_size).dot(buffer.segment(from, kernel_size)); + + if (!std::isfinite(result)) { + result = 0.0; + value_type av_weights = 0.0; + for (ssize_t k = from; k <= to; ++k, ++c) { + value_type neighbour_value = buffer(k); + if (std::isfinite (neighbour_value)) { + av_weights += kernel[c]; + result += neighbour_value * kernel[c]; + } + } + result /= av_weights; + } else if (kernel_size != kernel.size()) + result /= kernel.segment(c, kernel_size).sum(); + image.value() = result; + } + + private: + const default_type stdev; + ssize_t radius; + size_t axis; + Eigen::VectorXd kernel; + const bool zero_boundary; + const default_type spacing; + size_t m; + ssize_t buffer_size; + Eigen::VectorXd buffer; + }; }; //! @} } From afc8301573ec3080fc45e19fcfb56606520af6a8 Mon Sep 17 00:00:00 2001 From: Max Pietsch Date: Fri, 18 Nov 2016 10:23:05 +0000 Subject: [PATCH 237/723] mrfilter smoothing adapter replaced by threaded copy and in place smoothing. 20% speedup on 15 vol image --- cmd/mrfilter.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/mrfilter.cpp b/cmd/mrfilter.cpp index fd679cfc47..0a5b2ae197 100644 --- a/cmd/mrfilter.cpp +++ b/cmd/mrfilter.cpp @@ -1,16 +1,16 @@ /* * Copyright (c) 2008-2016 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/ - * + * * MRtrix is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * + * * For more details, see www.mrtrix.org - * + * */ @@ -234,7 +234,8 @@ void run () { Stride::set_from_command_line (filter); auto output = Image::create (argument[2], filter); - filter (input, output); + threaded_copy (input, output); + filter (output); break; } From dc4449055b1889a32be841814208519e3e052882 Mon Sep 17 00:00:00 2001 From: Max Pietsch Date: Fri, 18 Nov 2016 11:49:22 +0000 Subject: [PATCH 238/723] smooth filter cleanup --- lib/filter/smooth.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/filter/smooth.h b/lib/filter/smooth.h index d7ed145ad9..5ec214519a 100644 --- a/lib/filter/smooth.h +++ b/lib/filter/smooth.h @@ -217,11 +217,11 @@ namespace MR return; kernel.resize (2 * radius + 1); default_type norm_factor = 0.0; - for (size_t c = 0; c < kernel.size(); ++c) { + for (ssize_t c = 0; c < kernel.size(); ++c) { kernel[c] = exp(-((c-radius) * (c-radius) * spacing * spacing) / (2 * stdev * stdev)); norm_factor += kernel[c]; } - for (size_t c = 0; c < kernel.size(); c++) { + for (ssize_t c = 0; c < kernel.size(); c++) { kernel[c] /= norm_factor; } } @@ -281,7 +281,6 @@ namespace MR Eigen::VectorXd kernel; const bool zero_boundary; const default_type spacing; - size_t m; ssize_t buffer_size; Eigen::VectorXd buffer; }; From af15b005c4a2d498c7daa6456af002f5c378cc45 Mon Sep 17 00:00:00 2001 From: Max Pietsch Date: Sun, 20 Nov 2016 20:24:07 +0000 Subject: [PATCH 239/723] smooth filter: use inplace soothing in registration and gradient filter --- lib/filter/gradient.h | 3 ++- src/registration/nonlinear.h | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/filter/gradient.h b/lib/filter/gradient.h index 71b2ac58ff..2aa5b17bc3 100644 --- a/lib/filter/gradient.h +++ b/lib/filter/gradient.h @@ -115,7 +115,8 @@ namespace MR auto smoothed = Image::scratch (smoother); if (message.size()) smoother.set_message ("applying smoothing prior to calculating gradient"); - smoother (in, smoothed); + threaded_copy (in, smoothed); + smoother (smoothed); const size_t num_volumes = (in.ndim() == 3) ? 1 : in.size(3); diff --git a/src/registration/nonlinear.h b/src/registration/nonlinear.h index d38c79fb13..bfaa526b13 100644 --- a/src/registration/nonlinear.h +++ b/src/registration/nonlinear.h @@ -189,8 +189,8 @@ namespace MR DEBUG ("smoothing update fields"); Filter::Smooth smooth_filter (*im1_update); smooth_filter.set_stdev (update_smoothing_mm); - smooth_filter (*im1_update, *im1_update); - smooth_filter (*im2_update, *im2_update); + smooth_filter (*im1_update); + smooth_filter (*im2_update); } Image im1_deform_field = Image::scratch (field_header); @@ -205,8 +205,8 @@ namespace MR Filter::Smooth smooth_filter (*im1_to_mid_new); smooth_filter.set_stdev (disp_smoothing_mm); smooth_filter.set_zero_boundary (true); - smooth_filter (*im1_to_mid_new, *im1_to_mid_new); - smooth_filter (*im2_to_mid_new, *im2_to_mid_new); + smooth_filter (*im1_to_mid_new); + smooth_filter (*im2_to_mid_new); Registration::Warp::compose_linear_displacement (im1_to_mid_linear, *im1_to_mid_new, im1_deform_field); Registration::Warp::compose_linear_displacement (im2_to_mid_linear, *im2_to_mid_new, im2_deform_field); From 27461c3ad0780c26f30c08df96b1ad921b37bd92 Mon Sep 17 00:00:00 2001 From: Dave Raffelt Date: Mon, 21 Nov 2016 11:01:12 +1100 Subject: [PATCH 240/723] Renamed vector plot to fixel plot --- src/gui/mrview/displayable.h | 9 +- .../fixel.cpp => fixel/base_fixel.cpp} | 43 +++---- .../{vector/fixel.h => fixel/base_fixel.h} | 23 ++-- .../fixelfolder.cpp => fixel/directory.cpp} | 16 ++- .../fixelfolder.h => fixel/directory.h} | 12 +- .../{vector/vector.cpp => fixel/fixel.cpp} | 118 +++++++++--------- .../tool/{vector/vector.h => fixel/fixel.h} | 23 ++-- .../packedfixel.cpp => fixel/image4D.cpp} | 6 +- .../{vector/packedfixel.h => fixel/image4D.h} | 14 +-- .../sparsefixel.cpp => fixel/legacy.cpp} | 6 +- .../{vector/sparsefixel.h => fixel/legacy.h} | 12 +- .../tool/{vector => fixel}/vector_structs.h | 4 +- src/gui/mrview/tool/list.h | 4 +- 13 files changed, 144 insertions(+), 146 deletions(-) rename src/gui/mrview/tool/{vector/fixel.cpp => fixel/base_fixel.cpp} (93%) rename src/gui/mrview/tool/{vector/fixel.h => fixel/base_fixel.h} (95%) rename src/gui/mrview/tool/{vector/fixelfolder.cpp => fixel/directory.cpp} (88%) rename src/gui/mrview/tool/{vector/fixelfolder.h => fixel/directory.h} (74%) rename src/gui/mrview/tool/{vector/vector.cpp => fixel/fixel.cpp} (87%) rename src/gui/mrview/tool/{vector/vector.h => fixel/fixel.h} (83%) rename src/gui/mrview/tool/{vector/packedfixel.cpp => fixel/image4D.cpp} (97%) rename src/gui/mrview/tool/{vector/packedfixel.h => fixel/image4D.h} (72%) rename src/gui/mrview/tool/{vector/sparsefixel.cpp => fixel/legacy.cpp} (96%) rename src/gui/mrview/tool/{vector/sparsefixel.h => fixel/legacy.h} (76%) rename src/gui/mrview/tool/{vector => fixel}/vector_structs.h (96%) diff --git a/src/gui/mrview/displayable.h b/src/gui/mrview/displayable.h index 88a8536b11..a173ea2af8 100644 --- a/src/gui/mrview/displayable.h +++ b/src/gui/mrview/displayable.h @@ -22,6 +22,7 @@ #include "gui/projection.h" #include "gui/mrview/colourmap.h" + namespace MR { class ProgressBar; @@ -45,15 +46,15 @@ namespace MR const uint32_t LightingEnabled = 0x00800000; class Image; - namespace Tool { class AbstractFixel; } + namespace Tool { class BaseFixel; } namespace Tool { class Connectome; } namespace Tool { class Tractogram; } class DisplayableVisitor { public: - virtual void render_image_colourbar(const Image&) {} - virtual void render_fixel_colourbar(const Tool::AbstractFixel&) {} - virtual void render_tractogram_colourbar(const Tool::Tractogram&) {} + virtual void render_image_colourbar (const Image&) {} + virtual void render_fixel_colourbar (const Tool::BaseFixel&) {} + virtual void render_tractogram_colourbar (const Tool::Tractogram&) {} }; class Displayable : public QAction diff --git a/src/gui/mrview/tool/vector/fixel.cpp b/src/gui/mrview/tool/fixel/base_fixel.cpp similarity index 93% rename from src/gui/mrview/tool/vector/fixel.cpp rename to src/gui/mrview/tool/fixel/base_fixel.cpp index 5c7400e1fd..fd09a56bab 100644 --- a/src/gui/mrview/tool/vector/fixel.cpp +++ b/src/gui/mrview/tool/fixel/base_fixel.cpp @@ -13,7 +13,7 @@ * */ -#include "gui/mrview/tool/vector/fixel.h" +#include "gui/mrview/tool/fixel/base_fixel.h" namespace MR @@ -24,9 +24,7 @@ namespace MR { namespace Tool { - - - AbstractFixel::AbstractFixel (const std::string& filename, Vector& fixel_tool) : + BaseFixel::BaseFixel (const std::string& filename, Fixel& fixel_tool) : Displayable (filename), header (MR::Header::open (filename)), slice_fixel_indices (3), @@ -52,7 +50,7 @@ namespace MR voxel_size_length_multipler = 0.45 * (header.spacing(0) + header.spacing(1) + header.spacing(2)) / 3; } - AbstractFixel::~AbstractFixel() + BaseFixel::~BaseFixel() { MRView::GrabContext context; vertex_buffer.clear (); @@ -66,7 +64,7 @@ namespace MR regular_grid_val_buffer.clear (); } - std::string AbstractFixel::Shader::vertex_shader_source (const Displayable&) + std::string BaseFixel::Shader::vertex_shader_source (const Displayable&) { std::string source = "layout (location = 0) in vec3 centre;\n" @@ -90,7 +88,7 @@ namespace MR } - std::string AbstractFixel::Shader::geometry_shader_source (const Displayable& fixel) + std::string BaseFixel::Shader::geometry_shader_source (const Displayable& fixel) { std::string source = "layout(points) in;\n" @@ -176,7 +174,7 @@ namespace MR } - std::string AbstractFixel::Shader::fragment_shader_source (const Displayable&) + std::string BaseFixel::Shader::fragment_shader_source (const Displayable&) { std::string source = "out vec3 outColour;\n" @@ -188,9 +186,9 @@ namespace MR } - bool AbstractFixel::Shader::need_update (const Displayable& object) const + bool BaseFixel::Shader::need_update (const Displayable& object) const { - const AbstractFixel& fixel (dynamic_cast (object)); + const BaseFixel& fixel (dynamic_cast (object)); if (color_type != fixel.colour_type) return true; else if (scale_type != fixel.scale_type) @@ -199,9 +197,9 @@ namespace MR } - void AbstractFixel::Shader::update (const Displayable& object) + void BaseFixel::Shader::update (const Displayable& object) { - const AbstractFixel& fixel (dynamic_cast (object)); + const BaseFixel& fixel (dynamic_cast (object)); do_crop_to_slice = fixel.fixel_tool.do_crop_to_slice; color_type = fixel.colour_type; scale_type = fixel.scale_type; @@ -209,7 +207,7 @@ namespace MR } - void AbstractFixel::render (const Projection& projection) + void BaseFixel::render (const Projection& projection) { ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; start (fixel_shader); @@ -268,7 +266,7 @@ namespace MR } - void AbstractFixel::update_image_buffers () + void BaseFixel::update_image_buffers () { if (dir_buffer_dirty) reload_directions_buffer (); @@ -289,9 +287,9 @@ namespace MR } - void AbstractFixel::update_interp_image_buffer (const Projection& projection, - const MR::Header &fixel_header, - const MR::Transform &transform) + void BaseFixel::update_interp_image_buffer (const Projection& projection, + const MR::Header &fixel_header, + const MR::Transform &transform) { ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; // Code below "inspired" by ODF::draw @@ -414,7 +412,7 @@ namespace MR } - void AbstractFixel::load_image (const std::string& filename) + void BaseFixel::load_image (const std::string& filename) { // Make sure to set graphics context! // We're setting up vertex array objects @@ -470,7 +468,7 @@ namespace MR } - void AbstractFixel::reload_directions_buffer () + void BaseFixel::reload_directions_buffer () { MRView::GrabContext context; ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; @@ -487,7 +485,7 @@ namespace MR } - void AbstractFixel::reload_values_buffer () + void BaseFixel::reload_values_buffer () { MRView::GrabContext context; ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; @@ -510,7 +508,7 @@ namespace MR } - void AbstractFixel::reload_colours_buffer () + void BaseFixel::reload_colours_buffer () { MRView::GrabContext context; ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; @@ -533,7 +531,7 @@ namespace MR } - void AbstractFixel::reload_threshold_buffer () + void BaseFixel::reload_threshold_buffer () { MRView::GrabContext context; ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; @@ -551,7 +549,6 @@ namespace MR ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; } - } } } diff --git a/src/gui/mrview/tool/vector/fixel.h b/src/gui/mrview/tool/fixel/base_fixel.h similarity index 95% rename from src/gui/mrview/tool/vector/fixel.h rename to src/gui/mrview/tool/fixel/base_fixel.h index f47ffcec84..89802f29df 100644 --- a/src/gui/mrview/tool/vector/fixel.h +++ b/src/gui/mrview/tool/fixel/base_fixel.h @@ -27,8 +27,8 @@ #include "fixel/helpers.h" #include "gui/mrview/displayable.h" -#include "gui/mrview/tool/vector/vector.h" -#include "gui/mrview/tool/vector/vector_structs.h" +#include "gui/mrview/tool/fixel/fixel.h" +#include "gui/mrview/tool/fixel/vector_structs.h" namespace MR @@ -39,10 +39,11 @@ namespace MR { namespace Tool { - class AbstractFixel : public Displayable { + + class BaseFixel : public Displayable { public: - AbstractFixel (const std::string&, Vector&); - ~AbstractFixel(); + BaseFixel (const std::string&, Fixel&); + ~BaseFixel(); class Shader : public Displayable::Shader { public: @@ -280,7 +281,7 @@ namespace MR bool threshold_buffer_dirty; bool dir_buffer_dirty; private: - Vector& fixel_tool; + Fixel& fixel_tool; GL::VertexBuffer vertex_buffer; GL::VertexBuffer direction_buffer; GL::VertexBuffer colour_buffer; @@ -303,11 +304,11 @@ namespace MR // Wrapper to generically store fixel data - template class FixelType : public AbstractFixel + template class FixelType : public BaseFixel { public: - FixelType (const std::string& filename, Vector& fixel_tool) : - AbstractFixel (filename, fixel_tool), + FixelType (const std::string& filename, Fixel& fixel_tool) : + BaseFixel (filename, fixel_tool), transform (header) { } protected: @@ -319,8 +320,8 @@ namespace MR } }; - typedef MR::Fixel::Legacy::Image FixelSparseImageType; - typedef MR::Image FixelPackedImageType; + typedef MR::Fixel::Legacy::Image FixelLegacyType; + typedef MR::Image FixelImage4DType; typedef MR::Image FixelIndexImageType; } } diff --git a/src/gui/mrview/tool/vector/fixelfolder.cpp b/src/gui/mrview/tool/fixel/directory.cpp similarity index 88% rename from src/gui/mrview/tool/vector/fixelfolder.cpp rename to src/gui/mrview/tool/fixel/directory.cpp index 7b2b0e63b5..11c05dab2f 100644 --- a/src/gui/mrview/tool/vector/fixelfolder.cpp +++ b/src/gui/mrview/tool/fixel/directory.cpp @@ -13,7 +13,7 @@ * */ -#include "gui/mrview/tool/vector/fixelfolder.h" +#include "gui/mrview/tool/fixel/directory.h" namespace MR { @@ -23,7 +23,7 @@ namespace MR { namespace Tool { - void FixelFolder::load_image_buffer() + void Directory::load_image_buffer() { for (size_t axis = 0; axis < 3; ++axis) { slice_fixel_indices[axis].resize (fixel_data->size (axis)); @@ -60,7 +60,7 @@ namespace MR // Load fixel direction images - auto directions_image = Fixel::find_directions_header (Path::dirname (fixel_data->name())).get_image().with_direct_io (); + auto directions_image = MR::Fixel::find_directions_header (Path::dirname (fixel_data->name())).get_image().with_direct_io (); directions_image.index (1) = 0; for (auto l = Loop(0, 3) (*fixel_data); l; ++l) { fixel_data->index (3) = 0; @@ -75,7 +75,7 @@ namespace MR // Load fixel data images keys // We will load the actual fixel data lazily upon request - auto data_headers = Fixel::find_data_headers (Path::dirname (fixel_data->name ()), *fixel_data); + auto data_headers = MR::Fixel::find_data_headers (Path::dirname (fixel_data->name ()), *fixel_data); for (auto& header : data_headers) { if (header.size (1) != 1) continue; @@ -88,7 +88,7 @@ namespace MR } } - void FixelFolder::lazy_load_fixel_value_file (const std::string& key) const { + void Directory::lazy_load_fixel_value_file (const std::string& key) const { // We're assuming the key corresponds to the fixel data filename const auto data_filepath = Path::join(Path::dirname (fixel_data->name ()), key); @@ -99,7 +99,7 @@ namespace MR auto H = Header::open (data_filepath); - if (!Fixel::is_data_file (H)) + if (!MR::Fixel::is_data_file (H)) return; auto data_image = H.get_image (); @@ -122,7 +122,7 @@ namespace MR } - FixelValue& FixelFolder::get_fixel_value (const std::string& key) const { + FixelValue& Directory::get_fixel_value (const std::string& key) const { if (!has_values ()) return dummy_fixel_val_state; @@ -135,8 +135,6 @@ namespace MR } } - - } } } diff --git a/src/gui/mrview/tool/vector/fixelfolder.h b/src/gui/mrview/tool/fixel/directory.h similarity index 74% rename from src/gui/mrview/tool/vector/fixelfolder.h rename to src/gui/mrview/tool/fixel/directory.h index f50ac3ac37..cb47fd3c86 100644 --- a/src/gui/mrview/tool/vector/fixelfolder.h +++ b/src/gui/mrview/tool/fixel/directory.h @@ -12,10 +12,10 @@ * For more details, see www.mrtrix.org * */ -#ifndef __gui_mrview_tool_vector_fixelfolder_h__ -#define __gui_mrview_tool_vector_fixelfolder_h__ +#ifndef __gui_mrview_tool_fixel_directory_h__ +#define __gui_mrview_tool_fixel_directory_h__ -#include "gui/mrview/tool/vector/fixel.h" +#include "gui/mrview/tool/fixel/base_fixel.h" namespace MR { @@ -25,11 +25,11 @@ namespace MR { namespace Tool { - class FixelFolder : public FixelType + class Directory : public FixelType { public: - FixelFolder (const std::string& filename, Vector& fixel_tool) : - FixelType (Fixel::find_index_header (Path::dirname (filename)).name (), fixel_tool) + Directory (const std::string& filename, Fixel& fixel_tool) : + FixelType (MR::Fixel::find_index_header (Path::dirname (filename)).name (), fixel_tool) { value_types = {"unity"}; colour_types = {"direction"}; diff --git a/src/gui/mrview/tool/vector/vector.cpp b/src/gui/mrview/tool/fixel/fixel.cpp similarity index 87% rename from src/gui/mrview/tool/vector/vector.cpp rename to src/gui/mrview/tool/fixel/fixel.cpp index 5c80fa7cfd..14cbfafb5e 100644 --- a/src/gui/mrview/tool/vector/vector.cpp +++ b/src/gui/mrview/tool/fixel/fixel.cpp @@ -13,14 +13,14 @@ * */ -#include "gui/mrview/tool/vector/vector.h" +#include "gui/mrview/tool/fixel/fixel.h" #include "mrtrix.h" #include "gui/mrview/window.h" -#include "gui/mrview/tool/vector/fixel.h" -#include "gui/mrview/tool/vector/sparsefixel.h" -#include "gui/mrview/tool/vector/packedfixel.h" -#include "gui/mrview/tool/vector/fixelfolder.h" +#include "gui/mrview/tool/fixel/base_fixel.h" +#include "gui/mrview/tool/fixel/legacy.h" +#include "gui/mrview/tool/fixel/directory.h" +#include "gui/mrview/tool/fixel/image4D.h" #include "gui/dialog/file.h" #include "gui/mrview/tool/list_model_base.h" #include "math/rng.h" @@ -35,31 +35,31 @@ namespace MR { - class Vector::Model : public ListModelBase + class Fixel::Model : public ListModelBase { public: Model (QObject* parent) : ListModelBase (parent) { } - void add_items (std::vector& filenames, Vector& fixel_tool) { + void add_items (std::vector& filenames, Fixel& fixel_tool) { size_t old_size = items.size(); for (size_t i = 0, N = filenames.size(); i < N; ++i) { - AbstractFixel* fixel_image(nullptr); + BaseFixel* fixel_image(nullptr); try { if(Path::has_suffix (filenames[i], {".msf", ".msh"})) - fixel_image = new SparseFixel (filenames[i], fixel_tool); + fixel_image = new Legacy (filenames[i], fixel_tool); else - fixel_image = new FixelFolder (filenames[i], fixel_tool); + fixel_image = new Directory (filenames[i], fixel_tool); } catch (InvalidFixelDirectoryException &) { try { - fixel_image = new PackedFixel (filenames[i], fixel_tool); + fixel_image = new Image4D (filenames[i], fixel_tool); } catch (InvalidImageException& e) { @@ -80,14 +80,14 @@ namespace MR endInsertRows (); } - AbstractFixel* get_fixel_image (QModelIndex& index) { - return dynamic_cast(items[index.row()].get()); + BaseFixel* get_fixel_image (QModelIndex& index) { + return dynamic_cast(items[index.row()].get()); } }; - Vector::Vector (Dock* parent) : + Fixel::Fixel (Dock* parent) : Base (parent), do_lock_to_grid (true), do_crop_to_slice (true), @@ -256,35 +256,35 @@ namespace MR - void Vector::draw (const Projection& transform, bool is_3D, int, int) + void Fixel::draw (const Projection& transform, bool is_3D, int, int) { ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; not_3D = !is_3D; for (int i = 0; i < fixel_list_model->rowCount(); ++i) { if (fixel_list_model->items[i]->show && !hide_all_button->isChecked()) - dynamic_cast(fixel_list_model->items[i].get())->render (transform); + dynamic_cast(fixel_list_model->items[i].get())->render (transform); } ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; } - void Vector::draw_colourbars () + void Fixel::draw_colourbars () { if(hide_all_button->isChecked()) return; for (size_t i = 0, N = fixel_list_model->rowCount(); i < N; ++i) { if (fixel_list_model->items[i]->show) - dynamic_cast(fixel_list_model->items[i].get())->request_render_colourbar(*this); + dynamic_cast(fixel_list_model->items[i].get())->request_render_colourbar(*this); } } - size_t Vector::visible_number_colourbars () { + size_t Fixel::visible_number_colourbars () { size_t total_visible(0); if(!hide_all_button->isChecked()) { for (size_t i = 0, N = fixel_list_model->rowCount(); i < N; ++i) { - AbstractFixel* fixel = dynamic_cast(fixel_list_model->items[i].get()); + BaseFixel* fixel = dynamic_cast(fixel_list_model->items[i].get()); if (fixel && fixel->show && !ColourMap::maps[fixel->colourmap].special) total_visible += 1; } @@ -295,7 +295,7 @@ namespace MR - void Vector::render_fixel_colourbar(const Tool::AbstractFixel& fixel) + void Fixel::render_fixel_colourbar (const Tool::BaseFixel& fixel) { ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; float min_value = fixel.use_discard_lower() ? @@ -314,7 +314,7 @@ namespace MR } - void Vector::fixel_open_slot () + void Fixel::fixel_open_slot () { std::vector list = Dialog::File::get_files (this, "Select fixel images to open", @@ -323,7 +323,7 @@ namespace MR } - void Vector::add_images (std::vector &list) + void Fixel::add_images (std::vector &list) { if (list.empty()) return; @@ -343,7 +343,7 @@ namespace MR } - void Vector::dropEvent (QDropEvent* event) + void Fixel::dropEvent (QDropEvent* event) { static constexpr int max_files = 32; @@ -364,7 +364,7 @@ namespace MR } - void Vector::fixel_close_slot () + void Fixel::fixel_close_slot () { QModelIndexList indexes = fixel_list_view->selectionModel()->selectedIndexes(); while (indexes.size()) { @@ -375,7 +375,7 @@ namespace MR } - void Vector::toggle_shown_slot (const QModelIndex& index, const QModelIndex& index2) + void Fixel::toggle_shown_slot (const QModelIndex& index, const QModelIndex& index2) { if (index.row() == index2.row()) { fixel_list_view->setCurrentIndex(index); @@ -391,13 +391,13 @@ namespace MR } - void Vector::hide_all_slot () + void Fixel::hide_all_slot () { window().updateGL(); } - void Vector::update_gui_controls () + void Fixel::update_gui_controls () { update_gui_scaling_controls (); update_gui_threshold_controls (); @@ -405,7 +405,7 @@ namespace MR } - void Vector::update_gui_colour_controls (bool reload_colour_types) + void Fixel::update_gui_colour_controls (bool reload_colour_types) { QModelIndexList indices = fixel_list_view->selectionModel ()->selectedIndexes (); size_t n_images (indices.size ()); @@ -429,7 +429,7 @@ namespace MR int colourmap_index = -2; for (size_t i = 0; i < n_images; ++i) { - AbstractFixel* fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[i])); + BaseFixel* fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[i])); if (colourmap_index != int (fixel->colourmap)) { if (colourmap_index == -2) colourmap_index = fixel->colourmap; @@ -455,7 +455,7 @@ namespace MR colourmap_button->colourmap_actions[colourmap_index]->setChecked (true); } - AbstractFixel* first_fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[0])); + BaseFixel* first_fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[0])); if (n_images == 1 && reload_colour_types) first_fixel->load_colourby_combobox_options (*colour_combobox); @@ -477,7 +477,7 @@ namespace MR } - void Vector::update_gui_scaling_controls (bool reload_scaling_types) + void Fixel::update_gui_scaling_controls (bool reload_scaling_types) { QModelIndexList indices = fixel_list_view->selectionModel ()->selectedIndexes (); size_t n_images (indices.size ()); @@ -490,7 +490,7 @@ namespace MR return; } - AbstractFixel* first_fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[0])); + BaseFixel* first_fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[0])); if (n_images == 1 && reload_scaling_types) first_fixel->load_scaleby_combobox_options (*length_combobox); @@ -502,7 +502,7 @@ namespace MR } - void Vector::update_gui_threshold_controls (bool reload_threshold_types) + void Fixel::update_gui_threshold_controls (bool reload_threshold_types) { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); size_t n_images (indices.size ()); @@ -520,7 +520,7 @@ namespace MR return; } - AbstractFixel* first_fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[0])); + BaseFixel* first_fixel = dynamic_cast (fixel_list_model->get_fixel_image (indices[0])); bool has_val = first_fixel->has_values (); @@ -558,14 +558,14 @@ namespace MR } - void Vector::opacity_slot (int opacity) + void Fixel::opacity_slot (int opacity) { line_opacity = Math::pow2 (static_cast(opacity)) / 1.0e6f; window().updateGL(); } - void Vector::line_thickness_slot (int thickness) + void Fixel::line_thickness_slot (int thickness) { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); for (int i = 0; i < indices.size(); ++i) @@ -574,7 +574,7 @@ namespace MR } - void Vector::length_multiplier_slot () + void Fixel::length_multiplier_slot () { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); for (int i = 0; i < indices.size(); ++i) @@ -583,7 +583,7 @@ namespace MR } - void Vector::length_type_slot (int selection) + void Fixel::length_type_slot (int selection) { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); @@ -598,7 +598,7 @@ namespace MR } - void Vector::threshold_type_slot (int selection) + void Fixel::threshold_type_slot (int selection) { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); @@ -613,20 +613,20 @@ namespace MR } - void Vector::selection_changed_slot (const QItemSelection &, const QItemSelection &) + void Fixel::selection_changed_slot (const QItemSelection &, const QItemSelection &) { update_gui_controls (); } - void Vector::on_lock_to_grid_slot(bool is_checked) + void Fixel::on_lock_to_grid_slot(bool is_checked) { do_lock_to_grid = is_checked; window().updateGL(); } - void Vector::on_crop_to_slice_slot (bool is_checked) + void Fixel::on_crop_to_slice_slot (bool is_checked) { do_crop_to_slice = is_checked; lock_to_grid->setEnabled(do_crop_to_slice); @@ -635,7 +635,7 @@ namespace MR } - void Vector::toggle_show_colour_bar (bool visible, const ColourMapButton&) + void Fixel::toggle_show_colour_bar (bool visible, const ColourMapButton&) { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); for (int i = 0; i < indices.size(); ++i) @@ -644,7 +644,7 @@ namespace MR } - void Vector::selected_colourmap (size_t index, const ColourMapButton&) + void Fixel::selected_colourmap (size_t index, const ColourMapButton&) { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); for (int i = 0; i < indices.size(); ++i) { @@ -653,7 +653,7 @@ namespace MR window().updateGL(); } - void Vector::selected_custom_colour(const QColor& colour, const ColourMapButton&) + void Fixel::selected_custom_colour(const QColor& colour, const ColourMapButton&) { if (colour.isValid()) { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); @@ -665,7 +665,7 @@ namespace MR } } - void Vector::reset_colourmap (const ColourMapButton&) + void Fixel::reset_colourmap (const ColourMapButton&) { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); for (int i = 0; i < indices.size(); ++i) @@ -675,7 +675,7 @@ namespace MR } - void Vector::toggle_invert_colourmap (bool inverted, const ColourMapButton&) + void Fixel::toggle_invert_colourmap (bool inverted, const ColourMapButton&) { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); for (int i = 0; i < indices.size(); ++i) @@ -684,7 +684,7 @@ namespace MR } - void Vector::colour_changed_slot (int selection) + void Fixel::colour_changed_slot (int selection) { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); @@ -700,7 +700,7 @@ namespace MR } - void Vector::on_set_scaling_slot () + void Fixel::on_set_scaling_slot () { QModelIndexList indices = fixel_list_view->selectionModel()->selectedIndexes(); for (int i = 0; i < indices.size(); ++i) @@ -709,7 +709,7 @@ namespace MR } - void Vector::threshold_lower_changed (int) + void Fixel::threshold_lower_changed (int) { if (threshold_lower_box->checkState() == Qt::PartiallyChecked) return; threshold_lower->setEnabled (threshold_lower_box->isChecked()); @@ -722,7 +722,7 @@ namespace MR } - void Vector::threshold_upper_changed (int) + void Fixel::threshold_upper_changed (int) { if (threshold_upper_box->checkState() == Qt::PartiallyChecked) return; threshold_upper->setEnabled (threshold_upper_box->isChecked()); @@ -735,7 +735,7 @@ namespace MR } - void Vector::threshold_lower_value_changed () + void Fixel::threshold_lower_value_changed () { if (threshold_lower_box->checkState() == Qt::PartiallyChecked) return; if (threshold_lower_box->isChecked()) { @@ -752,7 +752,7 @@ namespace MR } - void Vector::threshold_upper_value_changed () + void Fixel::threshold_upper_value_changed () { if (threshold_upper_box->checkState() == Qt::PartiallyChecked) return; if (threshold_upper_box->isChecked()) { @@ -769,19 +769,19 @@ namespace MR } - void Vector::add_commandline_options (MR::App::OptionList& options) + void Fixel::add_commandline_options (MR::App::OptionList& options) { using namespace MR::App; options - + OptionGroup ("Vector plot tool options") + + OptionGroup ("Fixel plot tool options") - + Option ("vector.load", "Load the specified MRtrix sparse image file (.msf) into the fixel tool.") + + Option ("fixel.load", "Load a fixel file (any file inside a fixel directory, or an old *.msf legacy format file) into the fixel tool.") + Argument ("image").type_image_in(); } - bool Vector::process_commandline_option (const MR::App::ParsedOption& opt) + bool Fixel::process_commandline_option (const MR::App::ParsedOption& opt) { - if (opt.opt->is ("vector.load")) { + if (opt.opt->is ("fixel.load")) { std::vector list (1, std::string(opt[0])); try { fixel_list_model->add_items (list , *this); } catch (Exception& E) { E.display(); } diff --git a/src/gui/mrview/tool/vector/vector.h b/src/gui/mrview/tool/fixel/fixel.h similarity index 83% rename from src/gui/mrview/tool/vector/vector.h rename to src/gui/mrview/tool/fixel/fixel.h index b0cdcf1983..aa19980722 100644 --- a/src/gui/mrview/tool/vector/vector.h +++ b/src/gui/mrview/tool/fixel/fixel.h @@ -13,8 +13,8 @@ * */ -#ifndef __gui_mrview_tool_vector_h__ -#define __gui_mrview_tool_vector_h__ +#ifndef __gui_mrview_tool_fixel_h__ +#define __gui_mrview_tool_fixel_h__ #include "gui/mrview/tool/base.h" #include "gui/projection.h" @@ -22,6 +22,7 @@ #include "gui/mrview/combo_box_error.h" #include "gui/mrview/colourmap_button.h" + namespace MR { namespace GUI @@ -30,30 +31,30 @@ namespace MR { namespace Tool { - class Vector : public Base, public ColourMapButtonObserver, public DisplayableVisitor + class Fixel : public Base, public ColourMapButtonObserver, public DisplayableVisitor { Q_OBJECT public: class Model; - Vector (Dock* parent); + Fixel (Dock* parent); - virtual ~Vector () {} + virtual ~Fixel () {} void draw (const Projection& transform, bool is_3D, int, int) override; void draw_colourbars () override; size_t visible_number_colourbars () override; - void render_fixel_colourbar(const Tool::AbstractFixel& fixel) override; + void render_fixel_colourbar (const Tool::BaseFixel& fixel) override; static void add_commandline_options (MR::App::OptionList& options); virtual bool process_commandline_option (const MR::App::ParsedOption& opt) override; - void selected_colourmap(size_t index, const ColourMapButton&) override; - void selected_custom_colour(const QColor& colour, const ColourMapButton&) override; - void toggle_show_colour_bar(bool, const ColourMapButton&) override; - void toggle_invert_colourmap(bool, const ColourMapButton&) override; - void reset_colourmap(const ColourMapButton&) override; + void selected_colourmap (size_t index, const ColourMapButton&) override; + void selected_custom_colour (const QColor& colour, const ColourMapButton&) override; + void toggle_show_colour_bar (bool, const ColourMapButton&) override; + void toggle_invert_colourmap (bool, const ColourMapButton&) override; + void reset_colourmap (const ColourMapButton&) override; QPushButton* hide_all_button; bool do_lock_to_grid, do_crop_to_slice; diff --git a/src/gui/mrview/tool/vector/packedfixel.cpp b/src/gui/mrview/tool/fixel/image4D.cpp similarity index 97% rename from src/gui/mrview/tool/vector/packedfixel.cpp rename to src/gui/mrview/tool/fixel/image4D.cpp index 769ba1c8ff..564fbb9702 100644 --- a/src/gui/mrview/tool/vector/packedfixel.cpp +++ b/src/gui/mrview/tool/fixel/image4D.cpp @@ -13,7 +13,7 @@ * */ -#include "gui/mrview/tool/vector/packedfixel.h" +#include "gui/mrview/tool/fixel/image4D.h" namespace MR { @@ -23,7 +23,8 @@ namespace MR { namespace Tool { - void PackedFixel::load_image_buffer() + + void Image4D::load_image_buffer() { size_t ndim = fixel_data->ndim (); @@ -86,7 +87,6 @@ namespace MR } } } - } } } diff --git a/src/gui/mrview/tool/vector/packedfixel.h b/src/gui/mrview/tool/fixel/image4D.h similarity index 72% rename from src/gui/mrview/tool/vector/packedfixel.h rename to src/gui/mrview/tool/fixel/image4D.h index 25d7c92209..9f6b79568e 100644 --- a/src/gui/mrview/tool/vector/packedfixel.h +++ b/src/gui/mrview/tool/fixel/image4D.h @@ -12,10 +12,10 @@ * For more details, see www.mrtrix.org * */ -#ifndef __gui_mrview_tool_vector_packedfixel_h__ -#define __gui_mrview_tool_vector_packedfixel_h__ +#ifndef __gui_mrview_tool_fixel_image4D_h__ +#define __gui_mrview_tool_fixel_image4D_h__ -#include "gui/mrview/tool/vector/fixel.h" +#include "gui/mrview/tool/fixel/base_fixel.h" namespace MR { @@ -25,24 +25,24 @@ namespace MR { namespace Tool { - class PackedFixel : public FixelType + class Image4D : public FixelType { public: - PackedFixel (const std::string& filename, Vector& fixel_tool) : + Image4D (const std::string& filename, Fixel& fixel_tool) : FixelType (filename, fixel_tool) { value_types = {"Unity", "Length"}; colour_types = {"Direction", "Length"}; threshold_types = {"Length"}; fixel_values[value_types[1]]; - fixel_data.reset (new FixelPackedImageType (header.get_image ())); + fixel_data.reset (new FixelImage4DType (header.get_image ())); load_image (filename); } void load_image_buffer () override; }; - } + } } } } diff --git a/src/gui/mrview/tool/vector/sparsefixel.cpp b/src/gui/mrview/tool/fixel/legacy.cpp similarity index 96% rename from src/gui/mrview/tool/vector/sparsefixel.cpp rename to src/gui/mrview/tool/fixel/legacy.cpp index e5df0d4557..f9ceee9231 100644 --- a/src/gui/mrview/tool/vector/sparsefixel.cpp +++ b/src/gui/mrview/tool/fixel/legacy.cpp @@ -13,7 +13,7 @@ * */ -#include "gui/mrview/tool/vector/sparsefixel.h" +#include "gui/mrview/tool/fixel/legacy.h" namespace MR { @@ -23,7 +23,8 @@ namespace MR { namespace Tool { - void SparseFixel::load_image_buffer() + + void Legacy::load_image_buffer() { for (size_t axis = 0; axis < 3; ++axis) { const size_t axis_size = fixel_data->size (axis); @@ -66,7 +67,6 @@ namespace MR } } } - } } } diff --git a/src/gui/mrview/tool/vector/sparsefixel.h b/src/gui/mrview/tool/fixel/legacy.h similarity index 76% rename from src/gui/mrview/tool/vector/sparsefixel.h rename to src/gui/mrview/tool/fixel/legacy.h index 56ee1eb628..036daa8e5b 100644 --- a/src/gui/mrview/tool/vector/sparsefixel.h +++ b/src/gui/mrview/tool/fixel/legacy.h @@ -12,10 +12,10 @@ * For more details, see www.mrtrix.org * */ -#ifndef __gui_mrview_tool_vector_sparsefixel_h__ -#define __gui_mrview_tool_vector_sparsefixel_h__ +#ifndef __gui_mrview_tool_fixel_legacy_h__ +#define __gui_mrview_tool_fixel_legacy_h__ -#include "gui/mrview/tool/vector/fixel.h" +#include "gui/mrview/tool/fixel/base_fixel.h" namespace MR { @@ -25,10 +25,10 @@ namespace MR { namespace Tool { - class SparseFixel : public FixelType + class Legacy : public FixelType { public: - SparseFixel (const std::string& filename, Vector& fixel_tool) : + Legacy (const std::string& filename, Fixel& fixel_tool) : FixelType (filename, fixel_tool) { value_types = {"unity", "fixel size", "associated value"}; @@ -37,7 +37,7 @@ namespace MR fixel_values[value_types[1]]; fixel_values[value_types[2]]; - fixel_data.reset (new FixelSparseImageType (header)); + fixel_data.reset (new FixelLegacyType (header)); load_image (filename); } diff --git a/src/gui/mrview/tool/vector/vector_structs.h b/src/gui/mrview/tool/fixel/vector_structs.h similarity index 96% rename from src/gui/mrview/tool/vector/vector_structs.h rename to src/gui/mrview/tool/fixel/vector_structs.h index 4a7426257b..45f6246cec 100644 --- a/src/gui/mrview/tool/vector/vector_structs.h +++ b/src/gui/mrview/tool/fixel/vector_structs.h @@ -13,8 +13,8 @@ * */ -#ifndef __gui_mrview_tool_vector_structs_h__ -#define __gui_mrview_tool_vector_structs_h__ +#ifndef __gui_mrview_tool_fixel_structs_h__ +#define __gui_mrview_tool_fixel_structs_h__ namespace MR { diff --git a/src/gui/mrview/tool/list.h b/src/gui/mrview/tool/list.h index 2c3f75d651..f98b3df943 100644 --- a/src/gui/mrview/tool/list.h +++ b/src/gui/mrview/tool/list.h @@ -19,7 +19,7 @@ #include "gui/mrview/tool/roi_editor/roi.h" #include "gui/mrview/tool/overlay.h" #include "gui/mrview/tool/odf/odf.h" -#include "gui/mrview/tool/vector/vector.h" +#include "gui/mrview/tool/fixel/fixel.h" #include "gui/mrview/tool/screen_capture.h" #include "gui/mrview/tool/tractography/tractography.h" #include "gui/mrview/tool/connectome/connectome.h" @@ -36,7 +36,7 @@ TOOL(Overlay, Overlay, Overlay other images over the current image) TOOL(ROI, ROI editor, View & edit regions of interest) TOOL(Tractography, Tractography, Display tracks over the current image) TOOL(ODF, ODF Display, Display orientation density functions) -TOOL(Vector, Vector Plot, Plot vector images) +TOOL(Fixel, Fixel Plot, Plot fixel images) TOOL(Connectome, Connectome, Plot connectome properties) TOOL(Capture, Screen capture, Capture the screen as a png file) From 1ddefacdcbdf617157ff91d78dd78802f1f2363d Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Mon, 21 Nov 2016 09:13:27 +0000 Subject: [PATCH 241/723] memory alignment for Eigen: proposed solution This introduces a framework for dealing with over-aligned types that Eigen 3.3 provides, which are necessary to benefit from modern CPUs' AVX instructions. The issue applies to any class that might be dynamically allocated (via new/delete) and contains a static Eigen class that requires over-alignment (currently Vector4d, Matrix4f, Matrix2d, Matrix4d, transform_type, as far as I can tell). If dynamically allocated, alignment is not guaranteed (this is an issue that goes to the heart of the C++ standard), but this cannot be checked at compile-time. This makes it almost impossible to provide any guarantees that our code is safe since the testing framework only checks a limited range of the possibilities in the code, leading to random runtime failures. Eigen can verify alignment of the relevant classes within their constructors, but that will only catch issues at runtime. All this buys us is an immediate abort, and a more interpretable error message when this happens. But still no compile-time guarantees... So this commit attempts to provide a solution to this. It requires ALL classes to include the new MEMALIGN() macro, which will force the use of a over-aligned operator new() method, but crucially this will only force alignment for classes that genuinely need it (as reported by alignof(X) > default_alignment). For classes that don't need alignment, the default ::operator new() function is called, and hopefully any overhead optimised out (these should all be compile-time decisions). See cmd/test_alignment.cpp for a demonstration of this in use. Note that this command will be removed before merging to master, this is just a means of documenting the logic and testing done in the history for later inspection if required. The final piece of the puzzle will be to provide a means of inspecting the code to detect classes or structs that do not have MEMALIGN() specified. A script to do this will be added in a later commit. --- cmd/test_memalign.cpp | 109 ++++++++++++++++++++++++++++++++++++++++++ lib/header.h | 5 +- lib/image.h | 9 ++-- lib/types.h | 49 ++++--------------- 4 files changed, 128 insertions(+), 44 deletions(-) create mode 100644 cmd/test_memalign.cpp diff --git a/cmd/test_memalign.cpp b/cmd/test_memalign.cpp new file mode 100644 index 0000000000..8565554cd1 --- /dev/null +++ b/cmd/test_memalign.cpp @@ -0,0 +1,109 @@ +#include "command.h" +#include "image.h" + +using namespace MR; +using namespace App; + +void usage () +{ + AUTHOR = "me"; + + DESCRIPTION + + "Evaluate the amplitude of an image of spherical harmonic functions " + "along the specified directions"; + + REQUIRES_AT_LEAST_ONE_ARGUMENT = false; +} + + + +template size_t actual_alignof () { + size_t alignment = 10000; + for (size_t n = 0; n < 100; ++n) { + auto* x = new T; + size_t this_align = reinterpret_cast(x) & 127U; + if (this_align && this_align < alignment) + alignment = this_align; + auto ununsed = new int; // just to jitter alignment around a bit + } + if (alignment < alignof(T)) + WARN ("memory alignment failure!"); + return alignment; +} + + +// test classes: +// just try commenting out the MEMALIGN statement to check: +// - whether compiler catches the issue if it exists +// (i.e. alignment required > default, no operator new defined) +// - whether the class has an operator new method +// - what the stated compile-time alignment of the class is +// - whether the runtime alignment of the class is as expected + +class AlignedMember { MEMALIGN (AlignedMember) + public: + Eigen::Matrix4d M; +}; + +template +class AlignedMemberTemplate { MEMALIGN (AlignedMemberTemplate) + public: + Eigen::Matrix4d M; + T t; +}; + +template +class InheritsAlignedClass : public AlignedMember { MEMALIGN(InheritsAlignedClass) + public: + double x; +}; + +// looks like operator new is inherited by derived classes: +template +class InheritsAlignedClassNoOperatorNew : public AlignedMember { + public: + double x; +}; + +// but this does not apply if class is included as a regular member: +template +class IncludesAlignedClassNoOperatorNew { MEMALIGN(IncludesAlignedClassNoOperatorNew) + public: + AlignedMember a; + double x; +}; + + +#define RUNCHECKS(classname) \ + std::cerr << "\n######### " #classname " ##################\n"; \ + CHECK_MEM_ALIGN (classname); \ + VAR (alignof(classname)); \ + VAR (actual_alignof()); \ + VAR (__has_custom_new_operator::value) + +void run () +{ + RUNCHECKS (int); + RUNCHECKS (Eigen::Vector2f); + RUNCHECKS (Eigen::Vector3f); + RUNCHECKS (Eigen::Vector4f); + RUNCHECKS (Eigen::Vector2d); + RUNCHECKS (Eigen::Vector3d); + RUNCHECKS (Eigen::Vector4d); + RUNCHECKS (Eigen::Matrix2f); + RUNCHECKS (Eigen::Matrix3f); + RUNCHECKS (Eigen::Matrix4f); + RUNCHECKS (Eigen::Matrix2d); + RUNCHECKS (Eigen::Matrix3d); + RUNCHECKS (Eigen::Matrix4d); + + RUNCHECKS (AlignedMember); + RUNCHECKS (AlignedMemberTemplate); + RUNCHECKS (InheritsAlignedClass); + RUNCHECKS (InheritsAlignedClassNoOperatorNew); + RUNCHECKS (IncludesAlignedClassNoOperatorNew); + + RUNCHECKS (Header); + RUNCHECKS (Image); + RUNCHECKS (Image::Buffer); +} diff --git a/lib/header.h b/lib/header.h index 3e07d561e1..c1dcdd9440 100644 --- a/lib/header.h +++ b/lib/header.h @@ -17,6 +17,7 @@ #define __header_h__ #include +#include #include "debug.h" #include "types.h" @@ -42,7 +43,7 @@ namespace MR template class Image; - class Header { MEM_ALIGN + class Header { MEMALIGN (Header) public: class Axis; @@ -357,6 +358,8 @@ namespace MR } }; + CHECK_MEM_ALIGN (Header); + diff --git a/lib/image.h b/lib/image.h index cab78e31f4..b5dcca743a 100644 --- a/lib/image.h +++ b/lib/image.h @@ -35,7 +35,7 @@ namespace MR template - class Image { MEM_ALIGN + class Image { MEMALIGN (Image) public: typedef ValueType value_type; class Buffer; @@ -201,9 +201,9 @@ namespace MR return Header::scratch (template_header, label).get_image(); } - protected: //! shared reference to header/buffer std::shared_ptr buffer; + protected: //! pointer to data address whether in RAM or MMap void* data_pointer; //! voxel indices @@ -222,8 +222,9 @@ namespace MR template - class Image::Buffer : public Header { NO_MEM_ALIGN + class Image::Buffer : public Header { MEMALIGN (Image::Buffer) public: + Buffer() {} // TODO: delete this line! Only for testing memory alignment issues. //! construct a Buffer object to access the data in the image specified Buffer (Header& H, bool read_write_if_existing = false); Buffer (Buffer&&) = default; @@ -277,7 +278,7 @@ namespace MR // lightweight struct to copy data into: template - struct TmpImage { NO_MEM_ALIGN + struct TmpImage { MEMALIGN (TmpImage) typedef ValueType value_type; const typename Image::Buffer& b; diff --git a/lib/types.h b/lib/types.h index 52623cb8ed..d0b9deab3d 100644 --- a/lib/types.h +++ b/lib/types.h @@ -121,6 +121,9 @@ template class __has_custom_new_operator { enum { value = sizeof(test(nullptr)) == sizeof(char) }; }; + +template inline constexpr bool __need_to_mem_align () { return alignof (T) > MRTRIX_ALLOC_MEM_ALIGN; } + inline void* __aligned_alloc (std::size_t size) { auto* original = std::malloc (size + EIGEN_DEFAULT_ALIGN_BYTES); if (!original) throw std::bad_alloc(); @@ -131,35 +134,13 @@ inline void* __aligned_alloc (std::size_t size) { inline void __aligned_delete (void* ptr) { if (ptr) std::free (*(reinterpret_cast(ptr) - 1)); } -/*! \def NO_MEM_ALIGN - * used to signal that the class does not have special alignment - * requirements, and so does not need a custom operator new method. - * - * The compiler will check whether this is indeed the case, and fail with an - * appropriate warning if this is not true. In this case, you need to replace - * NO_MEM_ALIGN with MEM_ALIGN. - * \sa MEM_ALIGN - * \sa CHECK_MEM_ALIGN - */ -#define NO_MEM_ALIGN \ - void __check_memalign () { static_assert (alignof(*this) <= MRTRIX_ALLOC_MEM_ALIGN, "please change to MEM_ALIGN for this class"); } -/*! \def MEM_ALIGN - * used to signal that the class has special alignment requirements, and so - * needs a custom memory-aligned operator new method. - * - * The compiler will check whether this is indeed needed, and fail with an - * appropriate warning if this is not true. In this case, you need to replace - * MEM_ALIGN with NO_MEM_ALIGN. While it is technically safe to use an - * over-aligned allocator in this case, it will impact performance and memory - * efficiency, and so is to be avoided. - * \sa NO_MEM_ALIGN - * \sa CHECK_MEM_ALIGN - */ -#define MEM_ALIGN \ - void __check_memalign () { static_assert (alignof(*this) > MRTRIX_ALLOC_MEM_ALIGN, "no need to MEM_ALIGN: use NO_MEM_ALIGN here."); }\ - public: \ - EIGEN_MAKE_ALIGNED_OPERATOR_NEW +#define MEMALIGN(T) public: \ + void* operator new (std::size_t size) { return (alignof(T)>MRTRIX_ALLOC_MEM_ALIGN) ? __aligned_alloc (size) : ::operator new (size); } \ + void* operator new[] (std::size_t size) { return (alignof(T)>MRTRIX_ALLOC_MEM_ALIGN) ? __aligned_alloc (size) : ::operator new[] (size); } \ + void operator delete (void* ptr) { if (alignof(T)>MRTRIX_ALLOC_MEM_ALIGN) __aligned_delete (ptr); else ::operator delete (ptr); } \ + void operator delete[] (void* ptr) { if (alignof(T)>MRTRIX_ALLOC_MEM_ALIGN) __aligned_delete (ptr); else ::operator delete[] (ptr); } + /*! \def CHECK_MEM_ALIGN * used to verify that the class is set up approriately for memory alignment @@ -177,17 +158,7 @@ inline void __aligned_delete (void* ptr) { if (ptr) std::free (*(reinterpret_cas */ #define CHECK_MEM_ALIGN(classname) \ static_assert ( (alignof(classname) <= MRTRIX_ALLOC_MEM_ALIGN ) || __has_custom_new_operator::value, \ - "memory alignment not guaranteed\n\n" \ - " Memory alignment requirement for this class is larger than that guaranteed\n" \ - " by default allocator, and no custom operator new has been defined.\n" \ - " This can cause unexpected runtime errors with Eigen.\n" \ - " Please replace NO_MEM_ALIGN with MEM_ALIGN for this class.\n"); \ - static_assert ( (alignof(classname) > MRTRIX_ALLOC_MEM_ALIGN ) || !__has_custom_new_operator::value, \ - "unnecessary use of MEM_ALIGN\n\n" \ - " Memory alignment requirement for this class is already catered for by default allocator.\n" \ - " While this program will run fine as-is, using the non-default allocator does incur\n" \ - " a performance overhead. Please replace MEM_ALIGN with NO_MEM_ALIGN for this class.\n") - + "class requires over-alignment, but no operator new defined! Please insert MEMALIGN() into class definition.") namespace MR From 446a294c7895a328da55d465ff0a73df83566f99 Mon Sep 17 00:00:00 2001 From: Max Pietsch Date: Mon, 21 Nov 2016 17:58:17 +0000 Subject: [PATCH 242/723] population_template: proposed interface change for linear registration --- scripts/population_template | 124 +++++++++++++++++++++++++----------- 1 file changed, 88 insertions(+), 36 deletions(-) diff --git a/scripts/population_template b/scripts/population_template index 4e0897796e..76c2eff0fb 100755 --- a/scripts/population_template +++ b/scripts/population_template @@ -69,7 +69,11 @@ def check_linear_transformation (transformation, max_scaling = 0.5, max_shear = bGood = True runCommand ('transformcalc ' + transformation + ' decompose ' + transformation + 'decomp') - data = load_key_value(transformation + 'decomp') + if os.path.isfile(transformation + 'decomp'): # does not exist if run with -continue option + data = load_key_value(transformation + 'decomp') + else: + printMessage(transformation + 'decomp not found. skipping check') + return True os.remove(transformation + 'decomp') scaling = [float(x) for x in data['scaling']] if any([x < 0 for x in scaling]) or any([x > (1 + max_scaling) for x in scaling]) or any([x < (1 - max_scaling) for x in scaling]): @@ -96,8 +100,10 @@ class Input: self.mask_filename = mask_filename self.mask_directory = mask_directory -linear_scales = [0.3,0.4,0.6,0.8,1.0,1.0] -linear_lmax = [2,2,2,4,4,4] +rigid_scales = [0.3,0.4,0.6,0.8,1.0,1.0] +rigid_lmax = [2,2,2,4,4,4] +affine_scales = [0.3,0.4,0.6,0.8,1.0,1.0] +affine_lmax = [2,2,2,4,4,4] nl_scales = [0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0] nl_niter = [5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 ,5 ,5 ,5 ,5 ] @@ -109,26 +115,35 @@ lib.cmdlineParser.initialise('Generates an unbiased group-average template from lib.app.parser.add_argument('input_dir', help='The input directory containing all images used to build the template') lib.app.parser.add_argument('template', help='The output template image') -options = lib.app.parser.add_argument_group('Options for the population_template script') +linoptions = lib.app.parser.add_argument_group('Options for the linear registration') +linoptions.add_argument('-linear_type', help='Specifiy the linear registration mode. Options are "affine" (perform affine registration only), "rigid" (perform rigid registration only, this should be used for intra-subject registration in longitudinal analysis) and "rigid_affine" (perform rigid registration followed by affine registration, default).', default='rigid_affine') +linoptions.add_argument('-linear_no_pause', action='store_true', help='Do not pause the script if a linear registration seems implausible') +linoptions.add_argument('-linear_estimator', help='Choose estimator for intensity difference metric. Valid choices are: l1 (least absolute: |x|), l2 (ordinary least squares), lp (least powers: |x|^1.2), Default: l2') +linoptions.add_argument('-initial_alignment', default='mass', help='Method of alignment to form the initial template. Options are "mass" (default), "geometric" and "none".') +linoptions.add_argument('-rigid_scale', help='Specifiy the multi-resolution pyramid used to build the rigid template, in the form of a list of scale factors (default: %s). This and affine_scale implicitly define the number of template levels' % ','.join([str(x) for x in rigid_scales])) +linoptions.add_argument('-rigid_lmax', help='Specifiy the lmax used for rigid registration for each scale factor, in the form of a list of integers (default: %s). The list must be the same length as the linear_scale factor list' % ','.join([str(x) for x in rigid_lmax])) +linoptions.add_argument('-rigid_niter', help='Specifiy the number of registration iterations used within each level before updating the template, in the form of a list of integers (default:50 for each scale). This must be a single number or a list of same length as the linear_scale factor list') +linoptions.add_argument('-affine_scale', help='Specifiy the multi-resolution pyramid used to build the affine template, in the form of a list of scale factors (default: %s). This and rigid_scale implicitly define the number of template levels' % ','.join([str(x) for x in affine_scales])) +linoptions.add_argument('-affine_lmax', help='Specifiy the lmax used for affine registration for each scale factor, in the form of a list of integers (default: %s). The list must be the same length as the linear_scale factor list' % ','.join([str(x) for x in affine_lmax])) +linoptions.add_argument('-affine_niter', help='Specifiy the number of registration iterations used within each level before updating the template, in the form of a list of integers (default:500 for each scale). This must be a single number or a list of same length as the linear_scale factor list') + +nloptions = lib.app.parser.add_argument_group('Options for the non-linear registration') +nloptions.add_argument('-nl_scale', help='Specifiy the multi-resolution pyramid used to build the non-linear template, in the form of a list of scale factors (default: %s). This implicitly defines the number of template levels' % ','.join([str(x) for x in nl_scales])) +nloptions.add_argument('-nl_lmax', help='Specifiy the lmax used for non-linear registration for each scale factor, in the form of a list of integers (default: %s). The list must be the same length as the nl_scale factor list' % ','.join([str(x) for x in nl_lmax])) +nloptions.add_argument('-nl_niter', help='Specifiy the number of registration iterations used within each level before updating the template, in the form of a list of integers (default: %s). The list must be the same length as the nl_scale factor list' % ','.join([str(x) for x in nl_niter])) +nloptions.add_argument('-nl_update_smooth', default='2.0', help='Regularise the gradient update field with Gaussian smoothing (standard deviation in voxel units, Default 2.0 x voxel_size)') +nloptions.add_argument('-nl_disp_smooth', default='1.0', help='Regularise the displacement field with Gaussian smoothing (standard deviation in voxel units, Default 1.0 x voxel_size)') +nloptions.add_argument('-nl_grad_step', default='0.5', help='The gradient step size for non-linear registration (Default: 0.5)') + +options = lib.app.parser.add_argument_group('Optional input and output and general options') options.add_argument('-mask_dir', help='Optionally input a set of masks inside a single directory, one per input image (with the same file name prefix). Using masks will speed up registration significantly') options.add_argument('-warp_dir', help='Output a directory containing warps from each input to the template. If the folder does not exist it will be created') options.add_argument('-transformed_dir', help='Output a directory containing the input images transformed to the template. If the folder does not exist it will be created') options.add_argument('-linear_transformations_dir', help='Output a directory containing the linear transformations used to generate the template. If the folder does not exist it will be created') options.add_argument('-template_mask', help='Output a template mask. Only works in -mask_dir has been input. The template mask is computed as the intersection of all subject masks in template space.') -options.add_argument('-rigid', action='store_true', help='perform rigid registration instead of affine. This should be used for intra-subject registration in longitudinal analysis') -options.add_argument('-linear_no_pause', action='store_true', help='Do not pause the script if a linear registration seems implausible') -options.add_argument('-linear_scale', help='Specifiy the multi-resolution pyramid used to build the rigid or affine template, in the form of a list of scale factors (default: %s). This implicitly defines the number of template levels' % ','.join([str(x) for x in linear_scales])) -options.add_argument('-linear_lmax', help='Specifiy the lmax used for rigid or affine registration for each scale factor, in the form of a list of integers (default: %s). The list must be the same length as the linear_scale factor list' % ','.join([str(x) for x in linear_lmax])) -options.add_argument('-linear_niter', help='Specifiy the number of registration iterations used within each level before updating the template, in the form of a list of integers (default:500 for each scale). The must be a single number or a list of same length as the linear_scale factor list') -options.add_argument('-linear_estimator', help='Choose estimator for intensity difference metric. Valid choices are: l1 (least absolute: |x|), l2 (ordinary least squares), lp (least powers: |x|^1.2), Default: l2') -options.add_argument('-nl_scale', help='Specifiy the multi-resolution pyramid used to build the non-linear template, in the form of a list of scale factors (default: %s). This implicitly defines the number of template levels' % ','.join([str(x) for x in nl_scales])) -options.add_argument('-nl_lmax', help='Specifiy the lmax used for non-linear registration for each scale factor, in the form of a list of integers (default: %s). The list must be the same length as the nl_scale factor list' % ','.join([str(x) for x in nl_lmax])) -options.add_argument('-nl_niter', help='Specifiy the number of registration iterations used within each level before updating the template, in the form of a list of integers (default: %s). The list must be the same length as the nl_scale factor list' % ','.join([str(x) for x in nl_niter])) -options.add_argument('-nl_update_smooth', default='2.0', help='Regularise the gradient update field with Gaussian smoothing (standard deviation in voxel units, Default 2.0 x voxel_size)') -options.add_argument('-nl_disp_smooth', default='1.0', help='Regularise the displacement field with Gaussian smoothing (standard deviation in voxel units, Default 1.0 x voxel_size)') -options.add_argument('-nl_grad_step', default='0.5', help='The gradient step size for non-linear registration (Default: 0.5)') options.add_argument('-noreorientation', action='store_true', help='Turn off FOD reorientation in mrregister. Reorientation is on by default if the number of volumes in the 4th dimension corresponds to the number of coefficients in an antipodally symmetric spherical harmonic series (i.e. 6, 15, 28, 45, 66 etc') -options.add_argument('-initial_alignment', default='mass', help='Method of alignment to form the initial template. Options are "mass" (default), "geometric" and "none".') + + lib.app.initialise() @@ -186,7 +201,12 @@ for i in inFiles: noreorientation = lib.app.args.noreorientation -dorigid = lib.app.args.rigid + +dorigid = "rigid" in lib.app.args.linear_type +doaffine = "affine" in lib.app.args.linear_type +if not lib.app.args.linear_type in ["rigid", "affine", "rigid_affine"]: + errorMessage("linear_type must be one of " + str(["rigid", "affine", "rigid_affine"])) + do_pause_on_warn = True if lib.app.args.linear_no_pause: do_pause_on_warn = False @@ -219,21 +239,53 @@ if len(image_size) == 4: do_fod_registration = True -if lib.app.args.linear_scale: - linear_scales = [float(x) for x in lib.app.args.linear_scale.split(',')] -if lib.app.args.linear_lmax: - linear_lmax = [int(x) for x in lib.app.args.linear_lmax.split(',')] -linear_niter = [500] * len(linear_scales) -if lib.app.args.linear_niter: - linear_niter = [int(x) for x in lib.app.args.linear_niter.split(',')] - if len(linear_niter) == 1: - linear_niter = linear_niter * len(linear_scales) - elif len(linear_scales) != len(linear_niter): - errorMessage('linear_scales and linear_niter schedules are not equal in length') - -if do_fod_registration: - if len(linear_scales) != len(linear_lmax): - errorMessage('linear_scales and linear_lmax schedules are not equal in length') +if lib.app.args.affine_scale: + affine_scales = [float(x) for x in lib.app.args.affine_scale.split(',')] +if lib.app.args.affine_lmax: + affine_lmax = [int(x) for x in lib.app.args.affine_lmax.split(',')] +affine_niter = [500] * len(affine_scales) +if lib.app.args.affine_niter: + if not doaffine: + errorMessage("affine_niter specified when no affine registration is performed") + affine_niter = [int(x) for x in lib.app.args.affine_niter.split(',')] + if len(affine_niter) == 1: + affine_niter = affine_niter * len(affine_scales) + elif len(affine_scales) != len(affine_niter): + errorMessage('affine_scales and affine_niter schedules are not equal in length') + + +if lib.app.args.rigid_scale: + rigid_scales = [float(x) for x in lib.app.args.rigid_scale.split(',')] +if lib.app.args.rigid_lmax: + rigid_lmax = [int(x) for x in lib.app.args.rigid_lmax.split(',')] +rigid_niter = [50] * len(rigid_scales) +if lib.app.args.rigid_niter: + if not dorigid: + errorMessage("rigid_niter specified when no rigid registration is performed") + rigid_niter = [int(x) for x in lib.app.args.rigid_niter.split(',')] + if len(rigid_niter) == 1: + rigid_niter = rigid_niter * len(rigid_scales) + elif len(rigid_scales) != len(rigid_niter): + errorMessage('rigid_scales and rigid_niter schedules are not equal in length') + +linear_scales = [] +linear_lmax = [] +linear_niter = [] +linear_type = [] +if dorigid: + linear_scales += rigid_scales + linear_lmax += rigid_lmax + linear_niter += rigid_niter + linear_type += ['rigid'] * len(rigid_scales) + if do_fod_registration and len(rigid_scales) != len(rigid_lmax): + errorMessage('rigid_scales and rigid_lmax schedules are not equal in length') +if doaffine: + linear_scales += affine_scales + linear_lmax += affine_lmax + linear_niter += affine_niter + linear_type += ['affine'] * len(affine_scales) + if do_fod_registration and len(affine_scales) != len(affine_lmax): + errorMessage('affine_scales and affine_lmax schedules are not equal in length') datatype = ' -datatype float32' @@ -378,7 +430,7 @@ for level in range(0, len(linear_scales)): lmax = '' metric = '' mrregister_log = '' - if dorigid: + if linear_type[level] == 'rigid': scale = ' -rigid_scale ' + str(linear_scales[level]) niter = ' -rigid_niter ' + str(linear_niter[level]) regtype = ' -type rigid' @@ -426,7 +478,7 @@ for level in range(0, len(linear_scales)): # Here we ensure the template doesn't drift or scale runCommand('transformcalc ' + allindir('linear_transforms_%i' % level) + ' average linear_transform_average.txt -force -quiet') - if dorigid: + if linear_type[level] == 'rigid': runCommand('transformcalc linear_transform_average.txt rigid linear_transform_average.txt -force -quiet') runCommand('transformcalc linear_transform_average.txt invert linear_transform_average_inv.txt -force -quiet') @@ -471,7 +523,7 @@ for level in range(0, len(nl_scales)): scale = '' else: scale = ' -nl_scale ' + str(nl_scales[level]) - if dorigid: + if not doaffine: initialise = ' -rigid_init_matrix linear_transforms/' + i.prefix + '.txt' else: initialise = ' -affine_init_matrix linear_transforms/' + i.prefix + '.txt' From b9a37a0e2f719b91dda36624a672d3e73a09daef Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Tue, 22 Nov 2016 15:31:11 +0000 Subject: [PATCH 243/723] Eigen: remove unnecessary #pragma directives These warnings should now be avoided by including the Eigen folder using -isystem rather than -I on the compiler command line. --- lib/types.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/types.h b/lib/types.h index d0b9deab3d..500ff28e6d 100644 --- a/lib/types.h +++ b/lib/types.h @@ -24,10 +24,7 @@ #include // These lines are to silence deprecation warnings with Eigen & GCC v5 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" #include -#pragma GCC diagnostic pop /*! \defgroup VLA Variable-length array macros * From c161fd98ce96ea249f9dc8abc4f0593cbdc50170 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Tue, 22 Nov 2016 16:42:30 +0000 Subject: [PATCH 244/723] check_memalign: script to find instances of missing MEMALIGN macros The idea is to detect all classes or structs that are missing their MEMALIGN macro, so that we can verify the code is Eigen-safe. The NOMEMALIGN macro can also be used in select cases where the class is known not to be affected. Such classes will no show up in the output. However, this should only be used for sporadic cases, the normal case should be to declare the class with MEMALIGN. --- check_memalign | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100755 check_memalign diff --git a/check_memalign b/check_memalign new file mode 100755 index 0000000000..db3aa588fd --- /dev/null +++ b/check_memalign @@ -0,0 +1,26 @@ +#/bin/bash + +for f in $(find . -type f -name '*.h' -o -name '*.cpp' | grep -v '_moc.cpp'); do + + res=$( \ + cat $f | \ + grep -v '^#' | \ + perl -pe 's|//.*$||' | \ + tr '\n' ' ' | \ + perl -pe 's|\s+| |g' | \ + perl -pe 's|/\*.*?\*/||g' | \ + perl -pe 's|<[^{};<]*?>||g' | \ + perl -pe 's|<[^{};<]*?>||g' | \ + perl -pe 's|<[^{};<]*?>||g' | \ + perl -pe 's|<[^{};<]*?>||g' | \ + grep -Eo '\b(class|struct)\b[^;{]*?{(\s*(MEMALIGN\s*\([^\)]*\)|NOMEMALIGN))?' | \ + grep -Ev '\b(class|struct)\b[^;{]*?{(\s*(MEMALIGN\s*\([^\)]*\)|NOMEMALIGN))' + ) + + if [[ ! -z $res ]]; then + echo "################################### $f" + echo "$res" + fi + +done + From 8f26d78250f88ff2b48d8977d75478ff94a01cf7 Mon Sep 17 00:00:00 2001 From: Max Pietsch Date: Tue, 22 Nov 2016 17:05:30 +0000 Subject: [PATCH 245/723] population_template: make rigid, affine and nonlinear optional #827 --- scripts/population_template | 420 +++++++++++++++++++++--------------- 1 file changed, 241 insertions(+), 179 deletions(-) diff --git a/scripts/population_template b/scripts/population_template index 76c2eff0fb..8f390654a2 100755 --- a/scripts/population_template +++ b/scripts/population_template @@ -109,6 +109,8 @@ nl_scales = [0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0] nl_niter = [5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 ,5 ,5 ,5 ,5 ] nl_lmax = [2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4 ,4 ,4 ,4 ,4 ] +registration_modes = ["rigid","affine","nonlinear","rigid_affine","rigid_nonlinear","affine_nonlinear","rigid_affine_nonlinear"] + lib.app.author = 'David Raffelt (david.raffelt@florey.edu.au) & Max Pietsch (maximilian.pietsch@kcl.ac.uk) & Thijs Dhollander thijs.dhollander@gmail.com)' lib.cmdlineParser.initialise('Generates an unbiased group-average template from a series of images. First a template is optimised with linear registration (rigid or affine, affine is default), then non-linear registration is used to optimise the template further.') @@ -116,10 +118,8 @@ lib.app.parser.add_argument('input_dir', help='The input directory containing al lib.app.parser.add_argument('template', help='The output template image') linoptions = lib.app.parser.add_argument_group('Options for the linear registration') -linoptions.add_argument('-linear_type', help='Specifiy the linear registration mode. Options are "affine" (perform affine registration only), "rigid" (perform rigid registration only, this should be used for intra-subject registration in longitudinal analysis) and "rigid_affine" (perform rigid registration followed by affine registration, default).', default='rigid_affine') linoptions.add_argument('-linear_no_pause', action='store_true', help='Do not pause the script if a linear registration seems implausible') linoptions.add_argument('-linear_estimator', help='Choose estimator for intensity difference metric. Valid choices are: l1 (least absolute: |x|), l2 (ordinary least squares), lp (least powers: |x|^1.2), Default: l2') -linoptions.add_argument('-initial_alignment', default='mass', help='Method of alignment to form the initial template. Options are "mass" (default), "geometric" and "none".') linoptions.add_argument('-rigid_scale', help='Specifiy the multi-resolution pyramid used to build the rigid template, in the form of a list of scale factors (default: %s). This and affine_scale implicitly define the number of template levels' % ','.join([str(x) for x in rigid_scales])) linoptions.add_argument('-rigid_lmax', help='Specifiy the lmax used for rigid registration for each scale factor, in the form of a list of integers (default: %s). The list must be the same length as the linear_scale factor list' % ','.join([str(x) for x in rigid_lmax])) linoptions.add_argument('-rigid_niter', help='Specifiy the number of registration iterations used within each level before updating the template, in the form of a list of integers (default:50 for each scale). This must be a single number or a list of same length as the linear_scale factor list') @@ -135,7 +135,9 @@ nloptions.add_argument('-nl_update_smooth', default='2.0', help='Regularise the nloptions.add_argument('-nl_disp_smooth', default='1.0', help='Regularise the displacement field with Gaussian smoothing (standard deviation in voxel units, Default 1.0 x voxel_size)') nloptions.add_argument('-nl_grad_step', default='0.5', help='The gradient step size for non-linear registration (Default: 0.5)') -options = lib.app.parser.add_argument_group('Optional input and output and general options') +options = lib.app.parser.add_argument_group('Input, output and general options') +options.add_argument('-type', help='Specifiy the types of registration stages to perform. Options are "rigid" (perform rigid registration only which might be useful for intra-subject registration in longitudinal analysis), "affine" (perform affine registration) and "nonlinear" as well as cominations of registration types: %s. Default: rigid_affine_nonlinear' % ', '.join('"'+x+'"' for x in registration_modes if "_" in x), default='rigid_affine_nonlinear') +options.add_argument('-initial_alignment', default='mass', help='Method of alignment to form the initial template. Options are "mass" (default), "geometric" and "none".') options.add_argument('-mask_dir', help='Optionally input a set of masks inside a single directory, one per input image (with the same file name prefix). Using masks will speed up registration significantly') options.add_argument('-warp_dir', help='Output a directory containing warps from each input to the template. If the folder does not exist it will be created') options.add_argument('-transformed_dir', help='Output a directory containing the input images transformed to the template. If the folder does not exist it will be created') @@ -147,6 +149,14 @@ options.add_argument('-noreorientation', action='store_true', help='Turn off FOD lib.app.initialise() +if not lib.app.args.type in registration_modes: + errorMessage("registration type must be one of %s. provided: %s" % (str(["rigid", "affine", "rigid_affine"]), lib.app.args.type)) +dorigid = "rigid" in lib.app.args.type +doaffine = "affine" in lib.app.args.type +dolinear = dorigid or doaffine +dononlinear = "nonlinear" in lib.app.args.type +assert (dorigid + doaffine + dononlinear >= 1), "FIXME: registration type not valid" + lib.app.args.input_dir = relpath(lib.app.args.input_dir) inputDir = lib.app.args.input_dir if not os.path.exists(inputDir): @@ -163,7 +173,9 @@ if initial_alignment not in ["mass", "geometric", "none"]: linear_estimator = lib.app.args.linear_estimator if linear_estimator: - if lib.app.args.linear_estimator not in ["l1", "l1", "lp"]: + if not dononlinear: + errorMessage('linear_estimator specified when no linear registration is requested'); + if linear_estimator not in ["l1", "l1", "lp"]: errorMessage('linear_estimator must be one of ' + " ".join(["l1", "l1", "lp"])); useMasks = False @@ -202,14 +214,11 @@ for i in inFiles: noreorientation = lib.app.args.noreorientation -dorigid = "rigid" in lib.app.args.linear_type -doaffine = "affine" in lib.app.args.linear_type -if not lib.app.args.linear_type in ["rigid", "affine", "rigid_affine"]: - errorMessage("linear_type must be one of " + str(["rigid", "affine", "rigid_affine"])) - do_pause_on_warn = True if lib.app.args.linear_no_pause: do_pause_on_warn = False + if not dolinear: + errorMessage("linear option set when no linear registration is performed") lib.app.args.template = relpath(lib.app.args.template) lib.app.checkOutputFile(lib.app.args.template) @@ -223,6 +232,8 @@ if lib.app.args.transformed_dir: lib.app.checkOutputFile(lib.app.args.transformed_dir) if lib.app.args.linear_transformations_dir: + if not dolinear: + errorMessage("linear option set when no linear registration is performed") lib.app.args.linear_transformations_dir = relpath(lib.app.args.linear_transformations_dir) lib.app.checkOutputFile(lib.app.args.linear_transformations_dir) @@ -238,12 +249,45 @@ if len(image_size) == 4: printMessage("SH Series detected, performing FOD registration") do_fod_registration = True +#rigid options +if lib.app.args.rigid_scale: + rigid_scales = [float(x) for x in lib.app.args.rigid_scale.split(',')] + if not dorigid: + errorMessage("rigid_scales option set when no rigid registration is performed") +if lib.app.args.rigid_lmax: + if not dorigid: + errorMessage("rigid_lmax option set when no rigid registration is performed") + rigid_lmax = [int(x) for x in lib.app.args.rigid_lmax.split(',')] + if do_fod_registration and len(rigid_scales) != len(rigid_lmax): + errorMessage('rigid_scales and rigid_lmax schedules are not equal in length') +# if not do_fod_registration: + # rigid_lmax = [-1] * len(rigid_scales) + +rigid_niter = [100] * len(rigid_scales) +if lib.app.args.rigid_niter: + if not dorigid: + errorMessage("rigid_niter specified when no rigid registration is performed") + rigid_niter = [int(x) for x in lib.app.args.rigid_niter.split(',')] + if len(rigid_niter) == 1: + rigid_niter = rigid_niter * len(rigid_scales) + elif len(rigid_scales) != len(rigid_niter): + errorMessage('rigid_scales and rigid_niter schedules are not equal in length') +# affine options if lib.app.args.affine_scale: affine_scales = [float(x) for x in lib.app.args.affine_scale.split(',')] + if not doaffine: + errorMessage("affine_scale option set when no affine registration is performed") if lib.app.args.affine_lmax: + if not doaffine: + errorMessage("affine_lmax option set when no affine registration is performed") affine_lmax = [int(x) for x in lib.app.args.affine_lmax.split(',')] -affine_niter = [500] * len(affine_scales) + if do_fod_registration and len(affine_scales) != len(affine_lmax): + errorMessage('affine_scales and affine_lmax schedules are not equal in length') +# if not do_fod_registration: + # affine_lmax = [-1] * len(affine_scales) + +affine_niter = [500] * len(affine_scales) if lib.app.args.affine_niter: if not doaffine: errorMessage("affine_niter specified when no affine registration is performed") @@ -253,21 +297,6 @@ if lib.app.args.affine_niter: elif len(affine_scales) != len(affine_niter): errorMessage('affine_scales and affine_niter schedules are not equal in length') - -if lib.app.args.rigid_scale: - rigid_scales = [float(x) for x in lib.app.args.rigid_scale.split(',')] -if lib.app.args.rigid_lmax: - rigid_lmax = [int(x) for x in lib.app.args.rigid_lmax.split(',')] -rigid_niter = [50] * len(rigid_scales) -if lib.app.args.rigid_niter: - if not dorigid: - errorMessage("rigid_niter specified when no rigid registration is performed") - rigid_niter = [int(x) for x in lib.app.args.rigid_niter.split(',')] - if len(rigid_niter) == 1: - rigid_niter = rigid_niter * len(rigid_scales) - elif len(rigid_scales) != len(rigid_niter): - errorMessage('rigid_scales and rigid_niter schedules are not equal in length') - linear_scales = [] linear_lmax = [] linear_niter = [] @@ -277,32 +306,59 @@ if dorigid: linear_lmax += rigid_lmax linear_niter += rigid_niter linear_type += ['rigid'] * len(rigid_scales) - if do_fod_registration and len(rigid_scales) != len(rigid_lmax): - errorMessage('rigid_scales and rigid_lmax schedules are not equal in length') + if doaffine: linear_scales += affine_scales linear_lmax += affine_lmax linear_niter += affine_niter linear_type += ['affine'] * len(affine_scales) - if do_fod_registration and len(affine_scales) != len(affine_lmax): - errorMessage('affine_scales and affine_lmax schedules are not equal in length') -datatype = ' -datatype float32' +assert (len(linear_type) == len(linear_scales)) +assert (len(linear_scales) == len(linear_niter)) +if do_fod_registration: + assert (len(linear_lmax) == len(linear_niter)) -if lib.app.args.nl_scale: - nl_scales = [float(x) for x in lib.app.args.nl_scale.split(',')] -if lib.app.args.nl_niter: - nl_niter = [int(x) for x in lib.app.args.nl_niter.split(',')] -if lib.app.args.nl_lmax: - nl_lmax = [int(x) for x in lib.app.args.nl_lmax.split(',')] +printMessage('initial alignment of images: %s' % initial_alignment) +if dolinear: + printMessage('linear registration stages:') + if do_fod_registration: + for istage, [tpe, scale, lmax, niter] in enumerate(zip (linear_type, linear_scales, linear_lmax, linear_niter)): + printMessage('(%02i) %s scale: %.4f, niter: %i, lmax: %i' %(istage, tpe.ljust(9), scale, niter, lmax)) + else: + for istage, [tpe, scale, niter] in enumerate(zip (linear_type, linear_scales, linear_niter)): + printMessage('(%02i) %s scale: %.4f, niter: %i, no reorientation' %(istage, tpe.ljust(9), scale, niter)) -if len(nl_scales) != len(nl_niter): - errorMessage('nl_scales and nl_niter schedules are not equal in length') +datatype = ' -datatype float32' -if do_fod_registration: - if len(nl_scales) != len(nl_lmax): - errorMessage('nl_scales and nl_lmax schedules are not equal in length') +if not dononlinear: + nl_scales = [] + nl_lmax = [] + nl_niter = [] + if lib.app.args.warp_dir: + errorMessage('warp_dir specified when no nonlinear registration is performed') +else: + if lib.app.args.nl_scale: + nl_scales = [float(x) for x in lib.app.args.nl_scale.split(',')] + if lib.app.args.nl_niter: + nl_niter = [int(x) for x in lib.app.args.nl_niter.split(',')] + if lib.app.args.nl_lmax: + nl_lmax = [int(x) for x in lib.app.args.nl_lmax.split(',')] + + if len(nl_scales) != len(nl_niter): + errorMessage('nl_scales and nl_niter schedules are not equal in length') + + printMessage('nonlinear registration stages:') + if do_fod_registration: + if len(nl_scales) != len(nl_lmax): + errorMessage('nl_scales and nl_lmax schedules are not equal in length') + + if do_fod_registration: + for istage, [scale, lmax, niter] in enumerate(zip (nl_scales, nl_lmax, nl_niter)): + printMessage('(%02i) nonlinear scale: %.4f, niter: %i, lmax: %i' %(istage, scale, niter, lmax)) + else: + for istage, [scale, niter] in enumerate(zip (nl_scales, nl_niter)): + printMessage('(%02i) nonlinear scale: %.4f, niter: %i, no reorientation' %(istage, scale, niter)) lib.app.makeTempDir() lib.app.gotoTempDir() @@ -320,8 +376,6 @@ if useMasks: if lib.app.args.verbose: lib.app.make_dir('log') - - # Make initial template in average space printMessage('Generating initial template') input_filenames = [] @@ -348,6 +402,10 @@ if useMasks: if initial_alignment == 'none': for i in input: runCommand('mrtransform ' + abspath(i.directory, i.filename) + ' -interp linear -template average_header.mif input_transformed/' + i.prefix + '.mif' + datatype) + if not dolinear: + for i in input: + with open('linear_transforms_initial/%s.txt' % (i.prefix),'w') as fout: + fout.write('1 0 0 0\n0 1 0 0\n0 0 1 0\n0 0 0 1\n') else: mask = '' for i in input: @@ -392,7 +450,7 @@ else: runCommand('mrmath ' + ' '.join(mask_filenames) + ' max mask_translated.mif' ) runCommand('rm -f mask_translated_smooth.mif') runCommand('mrcrop ' + 'average_header.mif -mask mask_translated.mif average_header_cropped.mif') - # pad average space to allow for deviation from centre of mass alignment + # pad average space to allow for deviation from initial alignment runCommand('mrpad -uniform 10 average_header_cropped.mif -force average_header.mif') runCommand('rm -f average_header_cropped.mif') for i in input: @@ -418,91 +476,95 @@ runCommand('mrmath ' + allindir('input_transformed') + ' mean initial_template.m current_template = 'initial_template.mif' -# Optimise template with linear registration -printMessage('Optimising template with linear registration') -for level in range(0, len(linear_scales)): +# Optimise template with linear registration_modes +if not dolinear: for i in input: - initialise = '' - if useMasks: - mask = ' -mask1 ' + abspath(i.mask_directory, i.mask_filename) - else: - mask = '' - lmax = '' - metric = '' - mrregister_log = '' - if linear_type[level] == 'rigid': - scale = ' -rigid_scale ' + str(linear_scales[level]) - niter = ' -rigid_niter ' + str(linear_niter[level]) - regtype = ' -type rigid' - output = ' -rigid linear_transforms_%i/%s.txt' % (level, i.prefix) - if level > 0: - initialise = ' -rigid_init_matrix linear_transforms_%i/%s.txt' % (level-1, i.prefix) - if do_fod_registration: - lmax = ' -rigid_lmax ' + str(linear_lmax[level]) + runCommand('cp linear_transforms_initial/%s.txt linear_transforms/' % (i.prefix)) +else: + printMessage('Optimising template with linear registration') + for level in range(0, len(linear_scales)): + for i in input: + initialise = '' + if useMasks: + mask = ' -mask1 ' + abspath(i.mask_directory, i.mask_filename) else: - lmax = ' -noreorientation' - if linear_estimator: - metric = ' -rigid_metric.diff.estimator ' + linear_estimator - if lib.app.args.verbose: - mrregister_log = ' -info -rigid_log ' + 'log/' + i.filename + "_" + str(level) + '.log' - else: - scale = ' -affine_scale ' + str(linear_scales[level]) - niter = ' -affine_niter ' + str(linear_niter[level]) - regtype = ' -type affine' - output = ' -affine linear_transforms_%i/%s.txt' % (level, i.prefix) - if level > 0: - initialise = ' -affine_init_matrix linear_transforms_%i/%s.txt' % (level-1, i.prefix) - if do_fod_registration: - lmax = ' -affine_lmax ' + str(linear_lmax[level]) + mask = '' + lmax = '' + metric = '' + mrregister_log = '' + if linear_type[level] == 'rigid': + scale = ' -rigid_scale ' + str(linear_scales[level]) + niter = ' -rigid_niter ' + str(linear_niter[level]) + regtype = ' -type rigid' + output = ' -rigid linear_transforms_%i/%s.txt' % (level, i.prefix) + if level > 0: + initialise = ' -rigid_init_matrix linear_transforms_%i/%s.txt' % (level-1, i.prefix) + if do_fod_registration: + lmax = ' -rigid_lmax ' + str(linear_lmax[level]) + else: + lmax = ' -noreorientation' + if linear_estimator: + metric = ' -rigid_metric.diff.estimator ' + linear_estimator + if lib.app.args.verbose: + mrregister_log = ' -info -rigid_log ' + 'log/' + i.filename + "_" + str(level) + '.log' else: - lmax = ' -noreorientation' - if linear_estimator: - metric = ' -affine_metric.diff.estimator ' + linear_estimator - if lib.app.args.verbose: - mrregister_log = ' -info -affine_log ' + 'log/' + i.filename + "_" + str(level) + '.log' - - - runCommand('mrregister ' + abspath(i.directory, i.filename) + ' ' + current_template + - ' -force' + - initialise + - mask + - scale + - niter + - lmax + - regtype + - metric + - datatype + - output + - mrregister_log) - check_linear_transformation('linear_transforms_%i/%s.txt' % (level, i.prefix), pause_on_warn=do_pause_on_warn) - - # Here we ensure the template doesn't drift or scale - runCommand('transformcalc ' + allindir('linear_transforms_%i' % level) + ' average linear_transform_average.txt -force -quiet') - if linear_type[level] == 'rigid': - runCommand('transformcalc linear_transform_average.txt rigid linear_transform_average.txt -force -quiet') - runCommand('transformcalc linear_transform_average.txt invert linear_transform_average_inv.txt -force -quiet') + scale = ' -affine_scale ' + str(linear_scales[level]) + niter = ' -affine_niter ' + str(linear_niter[level]) + regtype = ' -type affine' + output = ' -affine linear_transforms_%i/%s.txt' % (level, i.prefix) + if level > 0: + initialise = ' -affine_init_matrix linear_transforms_%i/%s.txt' % (level-1, i.prefix) + if do_fod_registration: + lmax = ' -affine_lmax ' + str(linear_lmax[level]) + else: + lmax = ' -noreorientation' + if linear_estimator: + metric = ' -affine_metric.diff.estimator ' + linear_estimator + if lib.app.args.verbose: + mrregister_log = ' -info -affine_log ' + 'log/' + i.filename + "_" + str(level) + '.log' + + + runCommand('mrregister ' + abspath(i.directory, i.filename) + ' ' + current_template + + ' -force' + + initialise + + mask + + scale + + niter + + lmax + + regtype + + metric + + datatype + + output + + mrregister_log) + check_linear_transformation('linear_transforms_%i/%s.txt' % (level, i.prefix), pause_on_warn=do_pause_on_warn) + + # Here we ensure the template doesn't drift or scale + runCommand('transformcalc ' + allindir('linear_transforms_%i' % level) + ' average linear_transform_average.txt -force -quiet') + if linear_type[level] == 'rigid': + runCommand('transformcalc linear_transform_average.txt rigid linear_transform_average.txt -force -quiet') + runCommand('transformcalc linear_transform_average.txt invert linear_transform_average_inv.txt -force -quiet') - average_inv = loadtxt('linear_transform_average_inv.txt') - for i in input: - transform = dot(loadtxt('linear_transforms_%i/%s.txt' % (level, i.prefix)), average_inv) - savetxt('linear_transforms_%i/%s.txt'% (level, i.prefix), transform) + average_inv = loadtxt('linear_transform_average_inv.txt') + for i in input: + transform = dot(loadtxt('linear_transforms_%i/%s.txt' % (level, i.prefix)), average_inv) + savetxt('linear_transforms_%i/%s.txt'% (level, i.prefix), transform) - for i in input: - runCommand('mrtransform ' + abspath(i.directory, i.filename) + - ' -template ' + current_template + - ' -linear linear_transforms_%i/%s.txt' % (level, i.prefix) + - ' input_transformed/' + i.prefix + '.mif' + - datatype + - ' -force') + for i in input: + runCommand('mrtransform ' + abspath(i.directory, i.filename) + + ' -template ' + current_template + + ' -linear linear_transforms_%i/%s.txt' % (level, i.prefix) + + ' input_transformed/' + i.prefix + '.mif' + + datatype + + ' -force') - runCommand ('mrmath ' + allindir('input_transformed') + ' mean linear_template' + str(level) + '.mif -force') - current_template = 'linear_template' + str(level) + '.mif' + runCommand ('mrmath ' + allindir('input_transformed') + ' mean linear_template' + str(level) + '.mif -force') + current_template = 'linear_template' + str(level) + '.mif' -runCommand('cp ' + allindir('linear_transforms_%i' % level) + ' linear_transforms/') + runCommand('cp ' + allindir('linear_transforms_%i' % level) + ' linear_transforms/') # Create a template mask for nl registration by taking the intersection of all transformed input masks and dilating -if useMasks: +if useMasks and (dononlinear or lib.app.args.template_mask): for i in input: runCommand('mrtransform ' + abspath(i.mask_directory, i.mask_filename) + ' -template ' + current_template + @@ -512,72 +574,72 @@ if useMasks: ' -force') runCommand ('mrmath ' + allindir('masks_transformed') + ' min - | maskfilter - median - | maskfilter - dilate -npass 5 init_nl_template_mask.mif -force') current_template_mask = 'init_nl_template_mask.mif' - -# Optimise the template with non-linear registration -printMessage('Optimising template with non-linear registration') -lib.app.make_dir('warps') -for level in range(0, len(nl_scales)): - for i in input: - if level > 0: - initialise = ' -nl_init warps_%i/%s.mif' % (level-1, i.prefix) - scale = '' - else: - scale = ' -nl_scale ' + str(nl_scales[level]) - if not doaffine: - initialise = ' -rigid_init_matrix linear_transforms/' + i.prefix + '.txt' +if dononlinear: + # Optimise the template with non-linear registration + printMessage('Optimising template with non-linear registration') + lib.app.make_dir('warps') + for level in range(0, len(nl_scales)): + for i in input: + if level > 0: + initialise = ' -nl_init warps_%i/%s.mif' % (level-1, i.prefix) + scale = '' else: - initialise = ' -affine_init_matrix linear_transforms/' + i.prefix + '.txt' - - if useMasks: - mask = ' -mask1 ' + abspath(i.mask_directory, i.mask_filename) + ' -mask2 ' + current_template_mask - else: - mask = '' + scale = ' -nl_scale ' + str(nl_scales[level]) + if not doaffine: + initialise = ' -rigid_init_matrix linear_transforms/' + i.prefix + '.txt' + else: + initialise = ' -affine_init_matrix linear_transforms/' + i.prefix + '.txt' - if do_fod_registration: - lmax = ' -nl_lmax ' + str(nl_lmax[level]) - else: - lmax = ' -noreorientation' - - runCommand('mrregister ' + abspath(i.directory, i.filename) + ' ' + current_template + - ' -type nonlinear' + - ' -nl_niter ' + str(nl_niter[level]) + - ' -nl_warp_full warps_%i/%s.mif' % (level, i.prefix) + - ' -transformed input_transformed/' + i.prefix + '.mif' + - ' -nl_update_smooth ' + lib.app.args.nl_update_smooth + - ' -nl_disp_smooth ' + lib.app.args.nl_disp_smooth + - ' -nl_grad_step ' + lib.app.args.nl_grad_step + - ' -force ' + - initialise + - scale + - mask + - datatype + - lmax) - if level > 0: - runCommand('rm warps_%i/%s.mif' % (level-1, i.prefix)) - if useMasks: - runCommand('mrtransform ' + abspath(i.mask_directory, i.mask_filename) + - ' -template ' + current_template + - ' -warp_full warps_%i/%s.mif' % (level, i.prefix) + - ' masks_transformed/' + i.prefix + '.mif' + - ' -interp nearest ' + - ' -force') + if useMasks: + mask = ' -mask1 ' + abspath(i.mask_directory, i.mask_filename) + ' -mask2 ' + current_template_mask + else: + mask = '' - runCommand ('mrmath ' + allindir('input_transformed') + ' mean nl_template' + str(level) + '.mif') - current_template = 'nl_template' + str(level) + '.mif' + if do_fod_registration: + lmax = ' -nl_lmax ' + str(nl_lmax[level]) + else: + lmax = ' -noreorientation' - if useMasks: - runCommand ('mrmath ' + allindir('masks_transformed') + ' min - | maskfilter - median - | maskfilter - dilate -npass 5 nl_template_mask' + str(level) + '.mif') - current_template_mask = 'nl_template_mask' + str(level) + '.mif' + runCommand('mrregister ' + abspath(i.directory, i.filename) + ' ' + current_template + + ' -type nonlinear' + + ' -nl_niter ' + str(nl_niter[level]) + + ' -nl_warp_full warps_%i/%s.mif' % (level, i.prefix) + + ' -transformed input_transformed/' + i.prefix + '.mif' + + ' -nl_update_smooth ' + lib.app.args.nl_update_smooth + + ' -nl_disp_smooth ' + lib.app.args.nl_disp_smooth + + ' -nl_grad_step ' + lib.app.args.nl_grad_step + + ' -force ' + + initialise + + scale + + mask + + datatype + + lmax) + if level > 0: + runCommand('rm warps_%i/%s.mif' % (level-1, i.prefix)) + if useMasks: + runCommand('mrtransform ' + abspath(i.mask_directory, i.mask_filename) + + ' -template ' + current_template + + ' -warp_full warps_%i/%s.mif' % (level, i.prefix) + + ' masks_transformed/' + i.prefix + '.mif' + + ' -interp nearest ' + + ' -force') + + runCommand ('mrmath ' + allindir('input_transformed') + ' mean nl_template' + str(level) + '.mif') + current_template = 'nl_template' + str(level) + '.mif' - if level < len(nl_scales) - 1: - if (nl_scales[level] < nl_scales[level + 1]): - upsample_factor = nl_scales[level + 1] / nl_scales[level] + if useMasks: + runCommand ('mrmath ' + allindir('masks_transformed') + ' min - | maskfilter - median - | maskfilter - dilate -npass 5 nl_template_mask' + str(level) + '.mif') + current_template_mask = 'nl_template_mask' + str(level) + '.mif' + + if level < len(nl_scales) - 1: + if (nl_scales[level] < nl_scales[level + 1]): + upsample_factor = nl_scales[level + 1] / nl_scales[level] + for i in input: + runCommand('mrresize warps_%i/%s.mif -scale %f tmp.mif' % (level, i.prefix, upsample_factor)) + runCommand('mv tmp.mif warps_%i/%s.mif' % (level, i.prefix)) + else: for i in input: - runCommand('mrresize warps_%i/%s.mif -scale %f tmp.mif' % (level, i.prefix, upsample_factor)) - runCommand('mv tmp.mif warps_%i/%s.mif' % (level, i.prefix)) - else: - for i in input: - shutil.move('warps_%i/%s.mif' % (level, i.prefix), 'warps/') + shutil.move('warps_%i/%s.mif' % (level, i.prefix), 'warps/') runCommand('mrconvert ' + current_template + ' ' + getUserPath(lib.app.args.template, True) + lib.app.mrtrixForce) From 497f577c11f3494d15e193b2920025b89157b17e Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Tue, 22 Nov 2016 23:21:49 +0000 Subject: [PATCH 246/723] Eigen MEMALIGN: cmd/ folder updated --- cmd/5tt2gmwmi.cpp | 2 +- cmd/5ttedit.cpp | 2 +- cmd/afdconnectivity.cpp | 4 +- cmd/amp2sh.cpp | 4 +- cmd/dcmedit.cpp | 2 +- cmd/dcminfo.cpp | 2 +- cmd/dirflip.cpp | 4 +- cmd/dirgen.cpp | 4 +- cmd/dirmerge.cpp | 2 +- cmd/dirsplit.cpp | 4 +- cmd/dwi2adc.cpp | 2 +- cmd/dwi2fod.cpp | 6 +- cmd/dwi2tensor.cpp | 3 +- cmd/dwidenoise.cpp | 3 +- cmd/fixel2voxel.cpp | 60 +++++++------------- cmd/fod2dec.cpp | 6 +- cmd/fod2fixel.cpp | 3 +- cmd/mrcalc.cpp | 119 +++++++++++++++++++--------------------- cmd/mrcrop.cpp | 2 +- cmd/mrmath.cpp | 36 ++++++------ cmd/mrstats.cpp | 3 +- cmd/sh2amp.cpp | 4 +- cmd/sh2peaks.cpp | 12 ++-- cmd/shconv.cpp | 2 +- cmd/tckconvert.cpp | 9 +-- cmd/tckglobal.cpp | 2 +- cmd/tcknormalise.cpp | 9 +-- cmd/tcksample.cpp | 16 ++---- cmd/tckstats.cpp | 3 +- cmd/tensor2metric.cpp | 3 +- cmd/test_memalign.cpp | 109 ------------------------------------ lib/types.h | 16 +++--- 32 files changed, 151 insertions(+), 307 deletions(-) delete mode 100644 cmd/test_memalign.cpp diff --git a/cmd/5tt2gmwmi.cpp b/cmd/5tt2gmwmi.cpp index d1389c3ee7..e3828f2ed5 100644 --- a/cmd/5tt2gmwmi.cpp +++ b/cmd/5tt2gmwmi.cpp @@ -67,7 +67,7 @@ void usage () class Processor -{ +{ MEMALIGN(Processor) public: Processor (const Image& mask) : mask (mask) { } diff --git a/cmd/5ttedit.cpp b/cmd/5ttedit.cpp index 6fe252006c..1b0b113aff 100644 --- a/cmd/5ttedit.cpp +++ b/cmd/5ttedit.cpp @@ -68,7 +68,7 @@ void usage () class Modifier -{ +{ MEMALIGN(Modifier) public: Modifier (Image& input_image, Image& output_image) : v_in (input_image), diff --git a/cmd/afdconnectivity.cpp b/cmd/afdconnectivity.cpp index 2e0ce5eabf..ca61a806f0 100644 --- a/cmd/afdconnectivity.cpp +++ b/cmd/afdconnectivity.cpp @@ -90,7 +90,7 @@ typedef DWI::Tractography::SIFT::FixelBase FixelBase; class Fixel : public FixelBase -{ +{ MEMALIGN(Fixel) public: Fixel () : FixelBase (), length (0.0) { } Fixel (const FMLS::FOD_lobe& lobe) : FixelBase (lobe), length (0.0) { } @@ -111,7 +111,7 @@ class Fixel : public FixelBase class AFDConnectivity : public DWI::Tractography::SIFT::ModelBase -{ +{ MEMALIGN(AFDConnectivity) 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), diff --git a/cmd/amp2sh.cpp b/cmd/amp2sh.cpp index dbf6c1c6ce..7f794a9840 100644 --- a/cmd/amp2sh.cpp +++ b/cmd/amp2sh.cpp @@ -89,7 +89,7 @@ void usage () typedef float value_type; -class Amp2SHCommon { +class Amp2SHCommon { MEMALIGN(Amp2SHCommon) public: template Amp2SHCommon (const MatrixType& sh2amp, @@ -112,7 +112,7 @@ class Amp2SHCommon { -class Amp2SH { +class Amp2SH { MEMALIGN(Amp2SH) public: Amp2SH (const Amp2SHCommon& common) : C (common), diff --git a/cmd/dcmedit.cpp b/cmd/dcmedit.cpp index fe43f6e7b7..c58f15b0ee 100644 --- a/cmd/dcmedit.cpp +++ b/cmd/dcmedit.cpp @@ -62,7 +62,7 @@ void usage () } -class Tag { +class Tag { NOMEMALIGN public: Tag (uint16_t group, uint16_t element, const std::string& newvalue) : group (group), element (element), newvalue (newvalue) { } diff --git a/cmd/dcminfo.cpp b/cmd/dcminfo.cpp index b68ea29760..0c4d2aa572 100644 --- a/cmd/dcminfo.cpp +++ b/cmd/dcminfo.cpp @@ -48,7 +48,7 @@ void usage () } -class Tag { +class Tag { NOMEMALIGN public: uint16_t group, element; std::string value; diff --git a/cmd/dirflip.cpp b/cmd/dirflip.cpp index 3a5023e586..2588e9258d 100644 --- a/cmd/dirflip.cpp +++ b/cmd/dirflip.cpp @@ -58,7 +58,7 @@ typedef Eigen::Vector3d vector3_type; -class Shared { +class Shared { MEMALIGN(Shared) public: Shared (const Eigen::MatrixXd& directions, size_t target_num_permutations) : directions (directions), target_num_permutations (target_num_permutations), num_permutations(0), @@ -108,7 +108,7 @@ class Shared { -class Processor { +class Processor { MEMALIGN(Processor) public: Processor (Shared& shared) : shared (shared), diff --git a/cmd/dirgen.cpp b/cmd/dirgen.cpp index 6813aa338c..f7d762b2d2 100644 --- a/cmd/dirgen.cpp +++ b/cmd/dirgen.cpp @@ -66,7 +66,7 @@ OPTIONS // constrain directions to remain unit length: -class ProjectedUpdate { +class ProjectedUpdate { MEMALIGN(ProjectedUpdate) public: bool operator() ( Eigen::VectorXd& newx, @@ -82,7 +82,7 @@ class ProjectedUpdate { -class Energy { +class Energy { MEMALIGN(Energy) public: Energy (size_t n_directions, int power, bool bipolar, const Eigen::VectorXd& init_directions) : ndir (n_directions), diff --git a/cmd/dirmerge.cpp b/cmd/dirmerge.cpp index 6a4f59dda1..2b9e05f228 100644 --- a/cmd/dirmerge.cpp +++ b/cmd/dirmerge.cpp @@ -48,7 +48,7 @@ typedef double value_type; typedef std::array Direction; typedef std::vector DirectionSet; -struct OutDir { +struct OutDir { MEMALIGN(OutDir) Direction d; size_t b; size_t pe; diff --git a/cmd/dirsplit.cpp b/cmd/dirsplit.cpp index ea8454bbeb..be3223267a 100644 --- a/cmd/dirsplit.cpp +++ b/cmd/dirsplit.cpp @@ -53,7 +53,7 @@ typedef Eigen::Vector3d vector3_type; -class Shared { +class Shared { MEMALIGN(Shared) public: Shared (const Eigen::MatrixXd& directions, size_t num_subsets, size_t target_num_permutations) : directions (directions), subset (num_subsets), @@ -115,7 +115,7 @@ class Shared { -class EnergyCalculator { +class EnergyCalculator { MEMALIGN(EnergyCalculator) public: EnergyCalculator (Shared& shared) : shared (shared), subset (shared.get_init_subset()) { } diff --git a/cmd/dwi2adc.cpp b/cmd/dwi2adc.cpp index 6a60c32bcc..20f0cf615c 100644 --- a/cmd/dwi2adc.cpp +++ b/cmd/dwi2adc.cpp @@ -48,7 +48,7 @@ typedef float value_type; -class DWI2ADC { +class DWI2ADC { MEMALIGN(DWI2ADC) public: DWI2ADC (const Eigen::MatrixXd& binv, size_t dwi_axis) : dwi (binv.cols()), diff --git a/cmd/dwi2fod.cpp b/cmd/dwi2fod.cpp index 7e4a1dc693..678b17990c 100644 --- a/cmd/dwi2fod.cpp +++ b/cmd/dwi2fod.cpp @@ -99,8 +99,7 @@ void usage () -class CSD_Processor -{ +class CSD_Processor { MEMALIGN(CSD_Processor) public: CSD_Processor (const DWI::SDeconv::CSD::Shared& shared, Image& mask) : sdeconv (shared), @@ -166,8 +165,7 @@ class CSD_Processor -class MSMT_Processor -{ +class MSMT_Processor { MEMALIGN (MSMT_Processor) public: MSMT_Processor (const DWI::SDeconv::MSMT_CSD::Shared& shared, Image& mask_image, std::vector< Image > odf_images) : sdeconv (shared), diff --git a/cmd/dwi2tensor.cpp b/cmd/dwi2tensor.cpp index da5eab15be..f0446ce2bc 100644 --- a/cmd/dwi2tensor.cpp +++ b/cmd/dwi2tensor.cpp @@ -73,8 +73,7 @@ void usage () } template -class Processor -{ +class Processor { MEMALIGN(Processor) public: Processor (const Eigen::MatrixXd& b, const int iter, MASKType* mask_image, B0Type* b0_image, DKTType* dkt_image, PredictType* predict_image) : mask_image (mask_image), diff --git a/cmd/dwidenoise.cpp b/cmd/dwidenoise.cpp index e4a6adeba3..97a6704e3b 100644 --- a/cmd/dwidenoise.cpp +++ b/cmd/dwidenoise.cpp @@ -90,8 +90,7 @@ typedef float value_type; template -class DenoisingFunctor -{ +class DenoisingFunctor { MEMALIGN(DenoisingFunctor) public: DenoisingFunctor (ImageType& dwi, std::vector extent, Image& mask, ImageType& noise) : extent {{extent[0]/2, extent[1]/2, extent[2]/2}}, diff --git a/cmd/fixel2voxel.cpp b/cmd/fixel2voxel.cpp index fba75a877e..eb22b995c6 100644 --- a/cmd/fixel2voxel.cpp +++ b/cmd/fixel2voxel.cpp @@ -74,8 +74,7 @@ void usage () 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 (in press)"; + "Beyond Fractional Anisotropy: Extraction of bundle-specific structural metrics from crossing fibre models. " "NeuroImage, 2014 (in press)"; ARGUMENTS + Argument ("fixel_in", "the input sparse fixel image.").type_image_in () @@ -91,8 +90,7 @@ void usage () -class OpBase -{ +class OpBase { MEMALIGN(OpBase) protected: typedef Sparse::Image in_type; typedef Image out_type; @@ -109,8 +107,7 @@ class OpBase }; -class Mean : public OpBase -{ +class Mean : public OpBase { MEMALIGN(Mean) public: Mean (const bool weighted) : OpBase (weighted), @@ -140,8 +137,7 @@ class Mean : public OpBase default_type sum, sum_volumes; }; -class Sum : public OpBase -{ +class Sum : public OpBase { MEMALIGN(Sum) public: Sum (const bool weighted) : OpBase (weighted), @@ -167,8 +163,7 @@ class Sum : public OpBase default_type sum; }; -class Product : public OpBase -{ +class Product : public OpBase { MEMALIGN(Product) public: Product (const bool weighted) : OpBase (weighted), @@ -196,8 +191,7 @@ class Product : public OpBase default_type product; }; -class RMS : public OpBase -{ +class RMS : public OpBase { MEMALIGN(RMS) public: RMS (const bool weighted) : OpBase (weighted), @@ -227,8 +221,7 @@ class RMS : public OpBase default_type sum, sum_volumes; }; -class Var : public OpBase -{ +class Var : public OpBase { MEMALIGN(Var) public: Var (const bool weighted) : OpBase (weighted), @@ -286,8 +279,7 @@ class Var : public OpBase default_type sum_sqr; }; -class Std : public Var -{ +class Std : public Var { MEMALIGN(Std) public: Std (const bool weighted) : Var (weighted) { } @@ -301,8 +293,7 @@ class Std : public Var } }; -class Min : public OpBase -{ +class Min : public OpBase { MEMALIGN(Min) public: Min (const bool weighted) : OpBase (weighted), @@ -328,8 +319,7 @@ class Min : public OpBase default_type min; }; -class Max : public OpBase -{ +class Max : public OpBase { MEMALIGN(Max) public: Max (const bool weighted) : OpBase (weighted), @@ -355,8 +345,7 @@ class Max : public OpBase default_type max; }; -class AbsMax : public OpBase -{ +class AbsMax : public OpBase { MEMALIGN(AbsMax) public: AbsMax (const bool weighted) : OpBase (weighted), @@ -382,8 +371,7 @@ class AbsMax : public OpBase default_type max; }; -class MagMax : public OpBase -{ +class MagMax : public OpBase { MEMALIGN(MagMax) public: MagMax (const bool weighted) : OpBase (weighted), @@ -409,8 +397,7 @@ class MagMax : public OpBase default_type max; }; -class Count : public OpBase -{ +class Count : public OpBase { MEMALIGN(Count) public: Count (const bool weighted) : OpBase (weighted) @@ -427,8 +414,7 @@ class Count : public OpBase } }; -class Complexity : public OpBase -{ +class Complexity : public OpBase { MEMALIGN(Complexity) public: Complexity (const bool weighted) : OpBase (weighted), @@ -460,8 +446,7 @@ class Complexity : public OpBase default_type max, sum; }; -class SF : public OpBase -{ +class SF : public OpBase { MEMALIGN(SF) public: SF (const bool weighted) : OpBase (weighted), @@ -489,8 +474,7 @@ class SF : public OpBase default_type max, sum; }; -class DEC_unit : public OpBase -{ +class DEC_unit : public OpBase { MEMALIGN(DEC_unit) public: DEC_unit (const bool weighted) : OpBase (weighted), @@ -517,8 +501,7 @@ class DEC_unit : public OpBase Eigen::Vector3 sum_dec; }; -class DEC_scaled : public OpBase -{ +class DEC_scaled : public OpBase { MEMALIGN(DEC_scaled) public: DEC_scaled (const bool weighted) : OpBase (weighted), @@ -560,8 +543,7 @@ class DEC_scaled : public OpBase default_type sum_volume, sum_value; }; -class SplitSize : public OpBase -{ +class SplitSize : public OpBase { MEMALIGN(SplitSize) public: SplitSize (const bool weighted) : OpBase (weighted) @@ -583,8 +565,7 @@ class SplitSize : public OpBase } }; -class SplitValue : public OpBase -{ +class SplitValue : public OpBase { MEMALIGN(SplitValue) public: SplitValue (const bool weighted) : OpBase (weighted) @@ -606,8 +587,7 @@ class SplitValue : public OpBase } }; -class SplitDir : public OpBase -{ +class SplitDir : public OpBase { MEMALIGN(SplitDir) public: SplitDir (const bool weighted) : OpBase (weighted) diff --git a/cmd/fod2dec.cpp b/cmd/fod2dec.cpp index 30a0f55240..2efdb330e8 100644 --- a/cmd/fod2dec.cpp +++ b/cmd/fod2dec.cpp @@ -82,7 +82,7 @@ void usage () typedef float value_type; const value_type UNIT = 1.0 / std::sqrt(3.0); -class DecTransform { +class DecTransform { MEMALIGN(DecTransform) public: @@ -97,7 +97,7 @@ class DecTransform { }; -class DecComputer { +class DecComputer { MEMALIGN(DecComputer) private: @@ -156,7 +156,7 @@ class DecComputer { }; -class DecWeighter { +class DecWeighter { MEMALIGN(DecWeighter) private: diff --git a/cmd/fod2fixel.cpp b/cmd/fod2fixel.cpp index 0ac0bd23ce..0f2a5bb744 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -102,8 +102,7 @@ void usage () -class Segmented_FOD_receiver -{ +class Segmented_FOD_receiver { MEMALIGN(Segmented_FOD_receiver) public: Segmented_FOD_receiver (const Header& header) : diff --git a/cmd/mrcalc.cpp b/cmd/mrcalc.cpp index afcbb37370..f371f15d41 100644 --- a/cmd/mrcalc.cpp +++ b/cmd/mrcalc.cpp @@ -148,19 +148,19 @@ typedef cfloat complex_type; class Evaluator; -class Chunk : public std::vector { +class Chunk : public std::vector { NOMEMALIGN public: complex_type value; }; -class ThreadLocalStorageItem { +class ThreadLocalStorageItem { NOMEMALIGN public: Chunk chunk; copy_ptr> image; }; -class ThreadLocalStorage : public std::vector { +class ThreadLocalStorage : public std::vector { NOMEMALIGN public: void load (Chunk& chunk, Image& image) { @@ -196,8 +196,7 @@ class ThreadLocalStorage : public std::vector { -class LoadedImage -{ +class LoadedImage { NOMEMALIGN public: LoadedImage (std::shared_ptr>& i, const bool c) : image (i), @@ -209,7 +208,7 @@ class LoadedImage -class StackEntry { +class StackEntry { NOMEMALIGN public: StackEntry (const char* entry) : @@ -267,8 +266,7 @@ class StackEntry { std::map StackEntry::image_list; -class Evaluator -{ +class Evaluator { NOMEMALIGN public: Evaluator (const std::string& name, const char* format_string, bool complex_maps_to_real = false, bool real_maps_to_complex = false) : id (name), @@ -380,8 +378,7 @@ std::string operation_string (const StackEntry& entry) template -class UnaryEvaluator : public Evaluator -{ +class UnaryEvaluator : public Evaluator { NOMEMALIGN public: UnaryEvaluator (const std::string& name, Operation operation, const StackEntry& operand) : Evaluator (name, operation.format, operation.ZtoR, operation.RtoZ), @@ -409,8 +406,7 @@ class UnaryEvaluator : public Evaluator template -class BinaryEvaluator : public Evaluator -{ +class BinaryEvaluator : public Evaluator { NOMEMALIGN public: BinaryEvaluator (const std::string& name, Operation operation, const StackEntry& operand1, const StackEntry& operand2) : Evaluator (name, operation.format, operation.ZtoR, operation.RtoZ), @@ -443,8 +439,7 @@ class BinaryEvaluator : public Evaluator template -class TernaryEvaluator : public Evaluator -{ +class TernaryEvaluator : public Evaluator { NOMEMALIGN 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), @@ -599,7 +594,7 @@ void get_header (const StackEntry& entry, Header& header) -class ThreadFunctor { +class ThreadFunctor { NOMEMALIGN public: ThreadFunctor ( const std::vector& inner_axes, @@ -710,7 +705,7 @@ void run_operations (const std::vector& stack) OPERATIONS BASIC FRAMEWORK: **********************************************************************/ -class OpBase { +class OpBase { NOMEMALIGN public: OpBase (const char* format_string, bool complex_maps_to_real = false, bool real_map_to_complex = false) : format (format_string), @@ -720,7 +715,7 @@ class OpBase { const bool ZtoR, RtoZ; }; -class OpUnary : public OpBase { +class OpUnary : public OpBase { NOMEMALIGN 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) { } @@ -729,7 +724,7 @@ class OpUnary : public OpBase { }; -class OpBinary : public OpBase { +class OpBinary : public OpBase { NOMEMALIGN 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) { } @@ -737,7 +732,7 @@ class OpBinary : public OpBase { complex_type Z (complex_type a, complex_type b) const { throw Exception ("operation not supported!"); return a; } }; -class OpTernary : public OpBase { +class OpTernary : public OpBase { NOMEMALIGN 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) { } @@ -751,184 +746,184 @@ class OpTernary : public OpBase { UNARY OPERATIONS: **********************************************************************/ -class OpAbs : public OpUnary { +class OpAbs : public OpUnary { NOMEMALIGN public: OpAbs () : OpUnary ("|%1|", true) { } complex_type R (real_type v) const { return std::abs (v); } complex_type Z (complex_type v) const { return std::abs (v); } }; -class OpNeg : public OpUnary { +class OpNeg : public OpUnary { NOMEMALIGN public: OpNeg () : OpUnary ("-%1") { } complex_type R (real_type v) const { return -v; } complex_type Z (complex_type v) const { return -v; } }; -class OpSqrt : public OpUnary { +class OpSqrt : public OpUnary { NOMEMALIGN public: OpSqrt () : OpUnary ("sqrt (%1)") { } complex_type R (real_type v) const { return std::sqrt (v); } complex_type Z (complex_type v) const { return std::sqrt (v); } }; -class OpExp : public OpUnary { +class OpExp : public OpUnary { NOMEMALIGN public: OpExp () : OpUnary ("exp (%1)") { } complex_type R (real_type v) const { return std::exp (v); } complex_type Z (complex_type v) const { return std::exp (v); } }; -class OpLog : public OpUnary { +class OpLog : public OpUnary { NOMEMALIGN public: OpLog () : OpUnary ("log (%1)") { } complex_type R (real_type v) const { return std::log (v); } complex_type Z (complex_type v) const { return std::log (v); } }; -class OpLog10 : public OpUnary { +class OpLog10 : public OpUnary { NOMEMALIGN public: OpLog10 () : OpUnary ("log10 (%1)") { } complex_type R (real_type v) const { return std::log10 (v); } complex_type Z (complex_type v) const { return std::log10 (v); } }; -class OpCos : public OpUnary { +class OpCos : public OpUnary { NOMEMALIGN public: OpCos () : OpUnary ("cos (%1)") { } complex_type R (real_type v) const { return std::cos (v); } complex_type Z (complex_type v) const { return std::cos (v); } }; -class OpSin : public OpUnary { +class OpSin : public OpUnary { NOMEMALIGN public: OpSin () : OpUnary ("sin (%1)") { } complex_type R (real_type v) const { return std::sin (v); } complex_type Z (complex_type v) const { return std::sin (v); } }; -class OpTan : public OpUnary { +class OpTan : public OpUnary { NOMEMALIGN public: OpTan () : OpUnary ("tan (%1)") { } complex_type R (real_type v) const { return std::tan (v); } complex_type Z (complex_type v) const { return std::tan (v); } }; -class OpCosh : public OpUnary { +class OpCosh : public OpUnary { NOMEMALIGN public: OpCosh () : OpUnary ("cosh (%1)") { } complex_type R (real_type v) const { return std::cosh (v); } complex_type Z (complex_type v) const { return std::cosh (v); } }; -class OpSinh : public OpUnary { +class OpSinh : public OpUnary { NOMEMALIGN public: OpSinh () : OpUnary ("sinh (%1)") { } complex_type R (real_type v) const { return std::sinh (v); } complex_type Z (complex_type v) const { return std::sinh (v); } }; -class OpTanh : public OpUnary { +class OpTanh : public OpUnary { NOMEMALIGN public: OpTanh () : OpUnary ("tanh (%1)") { } complex_type R (real_type v) const { return std::tanh (v); } complex_type Z (complex_type v) const { return std::tanh (v); } }; -class OpAcos : public OpUnary { +class OpAcos : public OpUnary { NOMEMALIGN public: OpAcos () : OpUnary ("acos (%1)") { } complex_type R (real_type v) const { return std::acos (v); } }; -class OpAsin : public OpUnary { +class OpAsin : public OpUnary { NOMEMALIGN public: OpAsin () : OpUnary ("asin (%1)") { } complex_type R (real_type v) const { return std::asin (v); } }; -class OpAtan : public OpUnary { +class OpAtan : public OpUnary { NOMEMALIGN public: OpAtan () : OpUnary ("atan (%1)") { } complex_type R (real_type v) const { return std::atan (v); } }; -class OpAcosh : public OpUnary { +class OpAcosh : public OpUnary { NOMEMALIGN public: OpAcosh () : OpUnary ("acosh (%1)") { } complex_type R (real_type v) const { return std::acosh (v); } }; -class OpAsinh : public OpUnary { +class OpAsinh : public OpUnary { NOMEMALIGN public: OpAsinh () : OpUnary ("asinh (%1)") { } complex_type R (real_type v) const { return std::asinh (v); } }; -class OpAtanh : public OpUnary { +class OpAtanh : public OpUnary { NOMEMALIGN public: OpAtanh () : OpUnary ("atanh (%1)") { } complex_type R (real_type v) const { return std::atanh (v); } }; -class OpRound : public OpUnary { +class OpRound : public OpUnary { NOMEMALIGN public: OpRound () : OpUnary ("round (%1)") { } complex_type R (real_type v) const { return std::round (v); } }; -class OpCeil : public OpUnary { +class OpCeil : public OpUnary { NOMEMALIGN public: OpCeil () : OpUnary ("ceil (%1)") { } complex_type R (real_type v) const { return std::ceil (v); } }; -class OpFloor : public OpUnary { +class OpFloor : public OpUnary { NOMEMALIGN public: OpFloor () : OpUnary ("floor (%1)") { } complex_type R (real_type v) const { return std::floor (v); } }; -class OpReal : public OpUnary { +class OpReal : public OpUnary { NOMEMALIGN public: OpReal () : OpUnary ("real (%1)", true) { } complex_type Z (complex_type v) const { return v.real(); } }; -class OpImag : public OpUnary { +class OpImag : public OpUnary { NOMEMALIGN public: OpImag () : OpUnary ("imag (%1)", true) { } complex_type Z (complex_type v) const { return v.imag(); } }; -class OpPhase : public OpUnary { +class OpPhase : public OpUnary { NOMEMALIGN public: OpPhase () : OpUnary ("phase (%1)", true) { } complex_type Z (complex_type v) const { return std::arg (v); } }; -class OpConj : public OpUnary { +class OpConj : public OpUnary { NOMEMALIGN public: OpConj () : OpUnary ("conj (%1)") { } complex_type Z (complex_type v) const { return std::conj (v); } }; -class OpIsNaN : public OpUnary { +class OpIsNaN : public OpUnary { NOMEMALIGN public: OpIsNaN () : OpUnary ("isnan (%1)", true, false) { } complex_type R (real_type v) const { return std::isnan (v) != 0; } complex_type Z (complex_type v) const { return std::isnan (v.real()) != 0 || std::isnan (v.imag()) != 0; } }; -class OpIsInf : public OpUnary { +class OpIsInf : public OpUnary { NOMEMALIGN public: OpIsInf () : OpUnary ("isinf (%1)", true, false) { } complex_type R (real_type v) const { return std::isinf (v) != 0; } complex_type Z (complex_type v) const { return std::isinf (v.real()) != 0 || std::isinf (v.imag()) != 0; } }; -class OpFinite : public OpUnary { +class OpFinite : public OpUnary { NOMEMALIGN public: OpFinite () : OpUnary ("finite (%1)", true, false) { } complex_type R (real_type v) const { return std::isfinite (v) != 0; } @@ -940,92 +935,92 @@ class OpFinite : public OpUnary { BINARY OPERATIONS: **********************************************************************/ -class OpAdd : public OpBinary { +class OpAdd : public OpBinary { NOMEMALIGN public: OpAdd () : OpBinary ("(%1 + %2)") { } complex_type R (real_type a, real_type b) const { return a+b; } complex_type Z (complex_type a, complex_type b) const { return a+b; } }; -class OpSubtract : public OpBinary { +class OpSubtract : public OpBinary { NOMEMALIGN public: OpSubtract () : OpBinary ("(%1 - %2)") { } complex_type R (real_type a, real_type b) const { return a-b; } complex_type Z (complex_type a, complex_type b) const { return a-b; } }; -class OpMultiply : public OpBinary { +class OpMultiply : public OpBinary { NOMEMALIGN public: OpMultiply () : OpBinary ("(%1 * %2)") { } complex_type R (real_type a, real_type b) const { return a*b; } complex_type Z (complex_type a, complex_type b) const { return a*b; } }; -class OpDivide : public OpBinary { +class OpDivide : public OpBinary { NOMEMALIGN public: OpDivide () : OpBinary ("(%1 / %2)") { } complex_type R (real_type a, real_type b) const { return a/b; } complex_type Z (complex_type a, complex_type b) const { return a/b; } }; -class OpPow : public OpBinary { +class OpPow : public OpBinary { NOMEMALIGN public: OpPow () : OpBinary ("%1^%2") { } complex_type R (real_type a, real_type b) const { return std::pow (a, b); } complex_type Z (complex_type a, complex_type b) const { return std::pow (a, b); } }; -class OpMin : public OpBinary { +class OpMin : public OpBinary { NOMEMALIGN public: OpMin () : OpBinary ("min (%1, %2)") { } complex_type R (real_type a, real_type b) const { return std::min (a, b); } }; -class OpMax : public OpBinary { +class OpMax : public OpBinary { NOMEMALIGN public: OpMax () : OpBinary ("max (%1, %2)") { } complex_type R (real_type a, real_type b) const { return std::max (a, b); } }; -class OpLessThan : public OpBinary { +class OpLessThan : public OpBinary { NOMEMALIGN public: OpLessThan () : OpBinary ("(%1 < %2)") { } complex_type R (real_type a, real_type b) const { return a < b; } }; -class OpGreaterThan : public OpBinary { +class OpGreaterThan : public OpBinary { NOMEMALIGN public: OpGreaterThan () : OpBinary ("(%1 > %2)") { } complex_type R (real_type a, real_type b) const { return a > b; } }; -class OpLessThanOrEqual : public OpBinary { +class OpLessThanOrEqual : public OpBinary { NOMEMALIGN public: OpLessThanOrEqual () : OpBinary ("(%1 <= %2)") { } complex_type R (real_type a, real_type b) const { return a <= b; } }; -class OpGreaterThanOrEqual : public OpBinary { +class OpGreaterThanOrEqual : public OpBinary { NOMEMALIGN public: OpGreaterThanOrEqual () : OpBinary ("(%1 >= %2)") { } complex_type R (real_type a, real_type b) const { return a >= b; } }; -class OpEqual : public OpBinary { +class OpEqual : public OpBinary { NOMEMALIGN public: OpEqual () : OpBinary ("(%1 == %2)", true) { } complex_type R (real_type a, real_type b) const { return a == b; } complex_type Z (complex_type a, complex_type b) const { return a == b; } }; -class OpNotEqual : public OpBinary { +class OpNotEqual : public OpBinary { NOMEMALIGN public: OpNotEqual () : OpBinary ("(%1 != %2)", true) { } complex_type R (real_type a, real_type b) const { return a != b; } complex_type Z (complex_type a, complex_type b) const { return a != b; } }; -class OpComplex : public OpBinary { +class OpComplex : public OpBinary { NOMEMALIGN public: OpComplex () : OpBinary ("(%1 + %2 i)", false, true) { } complex_type R (real_type a, real_type b) const { return complex_type (a, b); } @@ -1041,7 +1036,7 @@ class OpComplex : public OpBinary { TERNARY OPERATIONS: **********************************************************************/ -class OpIf : public OpTernary { +class OpIf : public OpTernary { NOMEMALIGN public: OpIf () : OpTernary ("(%1 ? %2 : %3)") { } complex_type R (real_type a, real_type b, real_type c) const { return a ? b : c; } diff --git a/cmd/mrcrop.cpp b/cmd/mrcrop.cpp index a8541c5f37..bbad86eb27 100644 --- a/cmd/mrcrop.cpp +++ b/cmd/mrcrop.cpp @@ -79,7 +79,7 @@ void run () bounds[axis][1] = 0; } - struct BoundsCheck { + struct BoundsCheck { NOMEMALIGN std::vector>& overall_bounds; std::vector> bounds; BoundsCheck (std::vector>& overall_bounds) : overall_bounds (overall_bounds), bounds (overall_bounds) { } diff --git a/cmd/mrmath.cpp b/cmd/mrmath.cpp index 48004a7bea..54553e202e 100644 --- a/cmd/mrmath.cpp +++ b/cmd/mrmath.cpp @@ -75,7 +75,7 @@ void usage () typedef float value_type; -class Mean { +class Mean { NOMEMALIGN public: Mean () : sum (0.0), count (0) { } void operator() (value_type val) { @@ -93,7 +93,7 @@ class Mean { size_t count; }; -class Median { +class Median { NOMEMALIGN public: Median () { } void operator() (value_type val) { @@ -106,7 +106,7 @@ class Median { std::vector values; }; -class Sum { +class Sum { NOMEMALIGN public: Sum () : sum (0.0) { } void operator() (value_type val) { @@ -120,7 +120,7 @@ class Sum { }; -class Product { +class Product { NOMEMALIGN public: Product () : product (NAN) { } void operator() (value_type val) { @@ -134,7 +134,7 @@ class Product { }; -class RMS { +class RMS { NOMEMALIGN public: RMS() : sum (0.0), count (0) { } void operator() (value_type val) { @@ -152,7 +152,7 @@ class RMS { size_t count; }; -class NORM2 { +class NORM2 { NOMEMALIGN public: NORM2() : sum (0.0), count (0) { } void operator() (value_type val) { @@ -171,7 +171,7 @@ class NORM2 { }; -class Var { +class Var { NOMEMALIGN public: Var () : sum (0.0), sum_sqr (0.0), count (0) { } void operator() (value_type val) { @@ -191,14 +191,14 @@ class Var { }; -class Std : public Var { +class Std : public Var { NOMEMALIGN public: Std() : Var() { } value_type result () const { return std::sqrt (Var::result()); } }; -class Min { +class Min { NOMEMALIGN public: Min () : min (std::numeric_limits::infinity()) { } void operator() (value_type val) { @@ -210,7 +210,7 @@ class Min { }; -class Max { +class Max { NOMEMALIGN public: Max () : max (-std::numeric_limits::infinity()) { } void operator() (value_type val) { @@ -222,7 +222,7 @@ class Max { }; -class AbsMax { +class AbsMax { NOMEMALIGN public: AbsMax () : max (-std::numeric_limits::infinity()) { } void operator() (value_type val) { @@ -233,7 +233,7 @@ class AbsMax { value_type max; }; -class MagMax { +class MagMax { NOMEMALIGN public: MagMax () : max (-std::numeric_limits::infinity()) { } MagMax (const int i) : max (-std::numeric_limits::infinity()) { } @@ -250,7 +250,7 @@ class MagMax { template -class AxisKernel { +class AxisKernel { NOMEMALIGN public: AxisKernel (size_t axis) : axis (axis) { } @@ -269,7 +269,7 @@ class AxisKernel { -class ImageKernelBase { +class ImageKernelBase { NOMEMALIGN public: virtual void process (Header& image_in) = 0; virtual void write_back (Image& out) = 0; @@ -278,14 +278,14 @@ class ImageKernelBase { template -class ImageKernel : public ImageKernelBase { +class ImageKernel : public ImageKernelBase { NOMEMALIGN protected: - class InitFunctor { + class InitFunctor { NOMEMALIGN public: template void operator() (ImageType& out) const { out.value() = Operation(); } }; - class ProcessFunctor { + class ProcessFunctor { NOMEMALIGN public: template void operator() (ImageType1& out, ImageType2& in) const { @@ -294,7 +294,7 @@ class ImageKernel : public ImageKernelBase { out.value() = op; } }; - class ResultFunctor { + class ResultFunctor { NOMEMALIGN public: template void operator() (ImageType1& out, ImageType2& in) const { diff --git a/cmd/mrstats.cpp b/cmd/mrstats.cpp index dafbe49866..a93d52df25 100644 --- a/cmd/mrstats.cpp +++ b/cmd/mrstats.cpp @@ -62,8 +62,7 @@ OPTIONS typedef Stats::value_type value_type; typedef Stats::complex_type complex_type; -class Volume_loop -{ +class Volume_loop { NOMEMALIGN public: Volume_loop (Image& in) : image (in), diff --git a/cmd/sh2amp.cpp b/cmd/sh2amp.cpp index 0b69cbf7b6..43971d8a84 100644 --- a/cmd/sh2amp.cpp +++ b/cmd/sh2amp.cpp @@ -60,8 +60,7 @@ void usage () typedef float value_type; -class SH2Amp -{ +class SH2Amp { MEMALIGN(SH2Amp) public: template SH2Amp (const MatrixType& dirs, const size_t lmax, bool nonneg) @@ -86,7 +85,6 @@ void run () { auto sh_data = Image::open(argument[0]); Math::SH::check (sh_data); - sh_data.__check_memalign(); Header amp_header (sh_data); diff --git a/cmd/sh2peaks.cpp b/cmd/sh2peaks.cpp index bae31b2234..739b86fedc 100644 --- a/cmd/sh2peaks.cpp +++ b/cmd/sh2peaks.cpp @@ -80,8 +80,7 @@ typedef float value_type; -class Direction -{ +class Direction { MEMALIGN(Direction) public: Direction () : a (NAN) { } Direction (const Direction& d) : a (d.a), v (d.v) { } @@ -96,8 +95,7 @@ class Direction -class Item -{ +class Item { MEMALIGN(Item) public: Eigen::VectorXf data; ssize_t pos[3]; @@ -107,8 +105,7 @@ class Item -class DataLoader -{ +class DataLoader { MEMALIGN(DataLoader) public: DataLoader (Image& sh_data, Image* mask_data) : @@ -150,8 +147,7 @@ class DataLoader -class Processor -{ +class Processor { MEMALIGN(Processor) public: Processor (Image& dirs_data, Eigen::Matrix& directions, diff --git a/cmd/shconv.cpp b/cmd/shconv.cpp index e92a9b55ed..1eeef2b573 100644 --- a/cmd/shconv.cpp +++ b/cmd/shconv.cpp @@ -49,7 +49,7 @@ void usage () typedef float value_type; -class SConvFunctor { +class SConvFunctor { MEMALIGN(SConvFunctor) public: SConvFunctor (const size_t n, Image& mask, const Eigen::Matrix& response) : diff --git a/cmd/tckconvert.cpp b/cmd/tckconvert.cpp index 076fb867ac..8ffb0e3eb6 100644 --- a/cmd/tckconvert.cpp +++ b/cmd/tckconvert.cpp @@ -65,8 +65,7 @@ void usage () } -class VTKWriter: public WriterInterface -{ +class VTKWriter: public WriterInterface { MEMALIGN(VTKWriter) public: VTKWriter(const std::string& file) : VTKout (file) { // create and write header of VTK output file: @@ -123,8 +122,7 @@ class VTKWriter: public WriterInterface -class ASCIIReader: public ReaderInterface -{ +class ASCIIReader: public ReaderInterface { MEMALIGN(ASCIIReader) public: ASCIIReader(const std::string& file) { auto num = list.parse_scan_check(file); @@ -151,8 +149,7 @@ class ASCIIReader: public ReaderInterface }; -class ASCIIWriter: public WriterInterface -{ +class ASCIIWriter: public WriterInterface { MEMALIGN(ASCIIWriter) public: ASCIIWriter(const std::string& file) { count.push_back(0); diff --git a/cmd/tckglobal.cpp b/cmd/tckglobal.cpp index 3c6bb27f8d..44d7b44ecc 100644 --- a/cmd/tckglobal.cpp +++ b/cmd/tckglobal.cpp @@ -182,7 +182,7 @@ void usage () template -class __copy_fod { +class __copy_fod { MEMALIGN(__copy_fod) public: __copy_fod (const int lmax, const double weight, const bool apodise) : w(weight), a(apodise), apo (lmax), SH_out (Math::SH::NforL(lmax)) { } diff --git a/cmd/tcknormalise.cpp b/cmd/tcknormalise.cpp index 676cbcf1d5..04fb8ca713 100644 --- a/cmd/tcknormalise.cpp +++ b/cmd/tcknormalise.cpp @@ -47,8 +47,7 @@ typedef Tractography::Streamline TrackType; -class Loader -{ +class Loader { MEMALIGN(Loader) public: Loader (const std::string& file) : reader (file, properties) {} @@ -63,8 +62,7 @@ class Loader -class Warper -{ +class Warper { MEMALIGN(Warper) public: Warper (const Image& warp) : interp (warp) { } @@ -93,8 +91,7 @@ class Warper -class Writer -{ +class Writer { MEMALIGN(Writer) public: Writer (const std::string& file, const Tractography::Properties& properties) : progress ("normalising tracks"), diff --git a/cmd/tcksample.cpp b/cmd/tcksample.cpp index 1c65c9b420..b28d4bc634 100644 --- a/cmd/tcksample.cpp +++ b/cmd/tcksample.cpp @@ -87,8 +87,7 @@ typedef Eigen::VectorXf vector_type; -class TDI : public Image -{ +class TDI : public Image { MEMALIGN(TDI) public: TDI (const Header& H, const size_t num_tracks) : Image (Image::scratch (H, "TDI scratch image")), @@ -114,7 +113,7 @@ class TDI : public Image // Guarantees thread-safety -class Sampler { +class Sampler { MEMALIGN(Sampler) public: Sampler (Image& image, const stat_tck statistic, const bool precise, std::unique_ptr& precalc_tdi) : interp ((!precise && !precalc_tdi) ? new Interp::Linear> (image) : nullptr), @@ -193,7 +192,7 @@ class Sampler { } else if (statistic == MEDIAN) { // Should be a weighted median... // Just use the n.log(n) algorithm - class WeightSort { + class WeightSort { NOMEMALIGN public: WeightSort (const DWI::Tractography::Mapping::Voxel& voxel, const value_type value) : value (value), @@ -277,8 +276,7 @@ class Sampler { -class ReceiverBase -{ +class ReceiverBase { MEMALIGN(ReceiverBase) public: ReceiverBase (const size_t num_tracks) : received (0), @@ -307,8 +305,7 @@ class ReceiverBase }; -class Receiver_Statistic : private ReceiverBase -{ +class Receiver_Statistic : private ReceiverBase { MEMALIGN(Receiver_Statistic) public: Receiver_Statistic (const size_t num_tracks) : ReceiverBase (num_tracks), @@ -333,8 +330,7 @@ class Receiver_Statistic : private ReceiverBase -class Receiver_NoStatistic : private ReceiverBase -{ +class Receiver_NoStatistic : private ReceiverBase { MEMALIGN(Receiver_NoStatistic) public: Receiver_NoStatistic (const std::string& path, const size_t num_tracks, diff --git a/cmd/tckstats.cpp b/cmd/tckstats.cpp index 7d30b2caf6..a257cfec40 100644 --- a/cmd/tckstats.cpp +++ b/cmd/tckstats.cpp @@ -63,8 +63,7 @@ void usage () // Store length and weight of each streamline -class LW -{ +class LW { NOMEMALIGN public: LW (const float l, const float w) : length (l), weight (w) { } LW () : length (NaN), weight (NaN) { } diff --git a/cmd/tensor2metric.cpp b/cmd/tensor2metric.cpp index 011d511e76..2e555928e7 100644 --- a/cmd/tensor2metric.cpp +++ b/cmd/tensor2metric.cpp @@ -107,8 +107,7 @@ void usage () "Proc Intl Soc Mag Reson Med, 1997, 5, 1742"; } -class Processor -{ +class Processor { MEMALIGN(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, std::vector vals, int modulate) : mask_img (mask_img), diff --git a/cmd/test_memalign.cpp b/cmd/test_memalign.cpp deleted file mode 100644 index 8565554cd1..0000000000 --- a/cmd/test_memalign.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include "command.h" -#include "image.h" - -using namespace MR; -using namespace App; - -void usage () -{ - AUTHOR = "me"; - - DESCRIPTION - + "Evaluate the amplitude of an image of spherical harmonic functions " - "along the specified directions"; - - REQUIRES_AT_LEAST_ONE_ARGUMENT = false; -} - - - -template size_t actual_alignof () { - size_t alignment = 10000; - for (size_t n = 0; n < 100; ++n) { - auto* x = new T; - size_t this_align = reinterpret_cast(x) & 127U; - if (this_align && this_align < alignment) - alignment = this_align; - auto ununsed = new int; // just to jitter alignment around a bit - } - if (alignment < alignof(T)) - WARN ("memory alignment failure!"); - return alignment; -} - - -// test classes: -// just try commenting out the MEMALIGN statement to check: -// - whether compiler catches the issue if it exists -// (i.e. alignment required > default, no operator new defined) -// - whether the class has an operator new method -// - what the stated compile-time alignment of the class is -// - whether the runtime alignment of the class is as expected - -class AlignedMember { MEMALIGN (AlignedMember) - public: - Eigen::Matrix4d M; -}; - -template -class AlignedMemberTemplate { MEMALIGN (AlignedMemberTemplate) - public: - Eigen::Matrix4d M; - T t; -}; - -template -class InheritsAlignedClass : public AlignedMember { MEMALIGN(InheritsAlignedClass) - public: - double x; -}; - -// looks like operator new is inherited by derived classes: -template -class InheritsAlignedClassNoOperatorNew : public AlignedMember { - public: - double x; -}; - -// but this does not apply if class is included as a regular member: -template -class IncludesAlignedClassNoOperatorNew { MEMALIGN(IncludesAlignedClassNoOperatorNew) - public: - AlignedMember a; - double x; -}; - - -#define RUNCHECKS(classname) \ - std::cerr << "\n######### " #classname " ##################\n"; \ - CHECK_MEM_ALIGN (classname); \ - VAR (alignof(classname)); \ - VAR (actual_alignof()); \ - VAR (__has_custom_new_operator::value) - -void run () -{ - RUNCHECKS (int); - RUNCHECKS (Eigen::Vector2f); - RUNCHECKS (Eigen::Vector3f); - RUNCHECKS (Eigen::Vector4f); - RUNCHECKS (Eigen::Vector2d); - RUNCHECKS (Eigen::Vector3d); - RUNCHECKS (Eigen::Vector4d); - RUNCHECKS (Eigen::Matrix2f); - RUNCHECKS (Eigen::Matrix3f); - RUNCHECKS (Eigen::Matrix4f); - RUNCHECKS (Eigen::Matrix2d); - RUNCHECKS (Eigen::Matrix3d); - RUNCHECKS (Eigen::Matrix4d); - - RUNCHECKS (AlignedMember); - RUNCHECKS (AlignedMemberTemplate); - RUNCHECKS (InheritsAlignedClass); - RUNCHECKS (InheritsAlignedClassNoOperatorNew); - RUNCHECKS (IncludesAlignedClassNoOperatorNew); - - RUNCHECKS (Header); - RUNCHECKS (Image); - RUNCHECKS (Image::Buffer); -} diff --git a/lib/types.h b/lib/types.h index 500ff28e6d..8dba4ed798 100644 --- a/lib/types.h +++ b/lib/types.h @@ -149,15 +149,17 @@ inline void __aligned_delete (void* ptr) { if (ptr) std::free (*(reinterpret_cas * * The compiler will check whether this is indeed needed, and fail with an * appropriate warning if this is not true. In this case, you need to replace - * MEM_ALIGN with NO_MEM_ALIGN. - * \sa NO_MEM_ALIGN - * \sa MEM_ALIGN + * MEMALIGN with NOMEMALIGN. + * \sa NOMEMALIGN + * \sa MEMALIGN */ #define CHECK_MEM_ALIGN(classname) \ static_assert ( (alignof(classname) <= MRTRIX_ALLOC_MEM_ALIGN ) || __has_custom_new_operator::value, \ "class requires over-alignment, but no operator new defined! Please insert MEMALIGN() into class definition.") +#define NOMEMALIGN + namespace MR { @@ -167,7 +169,7 @@ namespace MR typedef std::complex cfloat; template - struct container_cast : public T { + struct container_cast : public T { MEMALIGN(container_cast) template container_cast (const U& x) : T (x.begin(), x.end()) { } @@ -184,14 +186,14 @@ namespace MR //! check whether type is complex: - template struct is_complex : std::false_type { }; - template struct is_complex> : std::true_type { }; + template struct is_complex : std::false_type { NOMEMALIGN }; + template struct is_complex> : std::true_type { NOMEMALIGN }; //! check whether type is compatible with MRtrix3's file IO backend: template struct is_data_type : - std::integral_constant::value || is_complex::value> { }; + std::integral_constant::value || is_complex::value> { NOMEMALIGN }; } From 96ebbd6425b8ad36996cf8ffeb4834a7ea3635fe Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Wed, 23 Nov 2016 00:10:50 +0000 Subject: [PATCH 247/723] Eigen MEMALIGN: minor cosmetic changes --- lib/types.h | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/types.h b/lib/types.h index 8dba4ed798..060407659c 100644 --- a/lib/types.h +++ b/lib/types.h @@ -119,9 +119,7 @@ template class __has_custom_new_operator { }; -template inline constexpr bool __need_to_mem_align () { return alignof (T) > MRTRIX_ALLOC_MEM_ALIGN; } - -inline void* __aligned_alloc (std::size_t size) { +inline void* __aligned_malloc (std::size_t size) { auto* original = std::malloc (size + EIGEN_DEFAULT_ALIGN_BYTES); if (!original) throw std::bad_alloc(); void *aligned = reinterpret_cast((reinterpret_cast(original) & ~(std::size_t(EIGEN_DEFAULT_ALIGN_BYTES-1))) + EIGEN_DEFAULT_ALIGN_BYTES); @@ -129,14 +127,14 @@ inline void* __aligned_alloc (std::size_t size) { return aligned; } -inline void __aligned_delete (void* ptr) { if (ptr) std::free (*(reinterpret_cast(ptr) - 1)); } +inline void __aligned_free (void* ptr) { if (ptr) std::free (*(reinterpret_cast(ptr) - 1)); } #define MEMALIGN(T) public: \ - void* operator new (std::size_t size) { return (alignof(T)>MRTRIX_ALLOC_MEM_ALIGN) ? __aligned_alloc (size) : ::operator new (size); } \ - void* operator new[] (std::size_t size) { return (alignof(T)>MRTRIX_ALLOC_MEM_ALIGN) ? __aligned_alloc (size) : ::operator new[] (size); } \ - void operator delete (void* ptr) { if (alignof(T)>MRTRIX_ALLOC_MEM_ALIGN) __aligned_delete (ptr); else ::operator delete (ptr); } \ - void operator delete[] (void* ptr) { if (alignof(T)>MRTRIX_ALLOC_MEM_ALIGN) __aligned_delete (ptr); else ::operator delete[] (ptr); } + FORCE_INLINE void* operator new (std::size_t size) { return (alignof(T)>MRTRIX_ALLOC_MEM_ALIGN) ? __aligned_malloc (size) : ::operator new (size); } \ + FORCE_INLINE void* operator new[] (std::size_t size) { return (alignof(T)>MRTRIX_ALLOC_MEM_ALIGN) ? __aligned_malloc (size) : ::operator new[] (size); } \ + FORCE_INLINE void operator delete (void* ptr) { if (alignof(T)>MRTRIX_ALLOC_MEM_ALIGN) __aligned_free (ptr); else ::operator delete (ptr); } \ + FORCE_INLINE void operator delete[] (void* ptr) { if (alignof(T)>MRTRIX_ALLOC_MEM_ALIGN) __aligned_free (ptr); else ::operator delete[] (ptr); } /*! \def CHECK_MEM_ALIGN From b5e99c38516e2fd0760f63867b2fe7e51f141454 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 23 Nov 2016 11:41:48 +1100 Subject: [PATCH 248/723] tckgen: Premature exit mechanism Provide test that will prematurely terminate streamlines generation if it is highly unlikely that the target number of streamlines will be reached. Closes #828. --- src/dwi/tractography/tracking/early_exit.cpp | 62 +++++++++++++++++ src/dwi/tractography/tracking/early_exit.h | 66 +++++++++++++++++++ .../tractography/tracking/write_kernel.cpp | 4 ++ src/dwi/tractography/tracking/write_kernel.h | 6 +- 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 src/dwi/tractography/tracking/early_exit.cpp create mode 100644 src/dwi/tractography/tracking/early_exit.h diff --git a/src/dwi/tractography/tracking/early_exit.cpp b/src/dwi/tractography/tracking/early_exit.cpp new file mode 100644 index 0000000000..e4327f0626 --- /dev/null +++ b/src/dwi/tractography/tracking/early_exit.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + + +#include "dwi/tractography/tracking/early_exit.h" + + +namespace MR +{ + namespace DWI + { + namespace Tractography + { + namespace Tracking + { + + + + bool EarlyExit::operator() (const Writer<>& writer) + { + if (++counter != next_test) + return false; + + const size_t num_attempts = writer.total_count; + const size_t num_tracks = writer.count; + if ((num_attempts / default_type(max_num_attempts) > TCKGEN_EARLY_EXIT_STOP_TESTING_PERCENTAGE) || + (num_tracks / default_type(max_num_tracks) > TCKGEN_EARLY_EXIT_STOP_TESTING_PERCENTAGE)) { + DEBUG ("tckgen early exit: No longer testing (tracking progressed beyond " + str(std::round(100.0*TCKGEN_EARLY_EXIT_STOP_TESTING_PERCENTAGE)) + "%)"); + next_test = 0; + return false; + } + + const Eigen::Array a ( { default_type(num_attempts-num_tracks), default_type(num_attempts-num_tracks) }); + const Eigen::Array b ( { default_type(num_tracks+1), default_type(num_tracks+1) }); + const Eigen::Array x ( { 1.0-(max_num_tracks/default_type(max_num_attempts)), 1.0 }); + const auto incomplete_betas = Eigen::betainc (a, b, x); + const default_type conditional = incomplete_betas[0] / incomplete_betas[1]; + + DEBUG ("tckgen early exit: Target " + str(max_num_tracks) + "/" + str(max_num_attempts) + " (" + str(max_num_tracks/default_type(max_num_attempts)) + "), current " + str(num_tracks) + "/" + str(num_attempts) + " (" + str(num_tracks/default_type(num_attempts)) + "), conditional probability " + str(conditional)); + next_test *= 2; + return (conditional < TCKGEN_EARLY_EXIT_PROB_THRESHOLD); + } + + + + } + } + } +} + diff --git a/src/dwi/tractography/tracking/early_exit.h b/src/dwi/tractography/tracking/early_exit.h new file mode 100644 index 0000000000..6b3939ab34 --- /dev/null +++ b/src/dwi/tractography/tracking/early_exit.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2008-2016 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/ + * + * MRtrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see www.mrtrix.org + * + */ + +#ifndef __dwi_tractography_tracking_early_exit_h__ +#define __dwi_tractography_tracking_early_exit_h__ + +#include + +#include "dwi/tractography/file.h" +#include "dwi/tractography/tracking/shared.h" + + +#define TCKGEN_EARLY_EXIT_PROB_THRESHOLD 0.01 +#define TCKGEN_EARLY_EXIT_STOP_TESTING_PERCENTAGE 0.25 + + + +namespace MR +{ + namespace DWI + { + namespace Tractography + { + namespace Tracking + { + + + class EarlyExit + { + public: + EarlyExit (const SharedBase& shared) : + max_num_attempts (shared.max_num_attempts), + max_num_tracks (shared.max_num_tracks), + counter (0), + next_test ((max_num_attempts && max_num_tracks) ? (10 * max_num_attempts / max_num_tracks) : 0) { } + + bool operator() (const Writer<>&); + + private: + const size_t max_num_attempts, max_num_tracks; + size_t counter, next_test; + + }; + + + + } + } + } +} + +#endif + + diff --git a/src/dwi/tractography/tracking/write_kernel.cpp b/src/dwi/tractography/tracking/write_kernel.cpp index 71c5a97150..2333eb5a1e 100644 --- a/src/dwi/tractography/tracking/write_kernel.cpp +++ b/src/dwi/tractography/tracking/write_kernel.cpp @@ -37,6 +37,10 @@ namespace MR } writer (tck); progress.update ([&](){ return printf ("%8" PRIu64 " generated, %8" PRIu64 " selected", writer.total_count, writer.count); }, always_increment ? true : tck.size()); + if (early_exit (writer)) { + WARN ("Track generation terminating prematurely: Highly unlikely to reach target number of streamlines (p<" + str(TCKGEN_EARLY_EXIT_PROB_THRESHOLD,1) + ")"); + return false; + } return true; } diff --git a/src/dwi/tractography/tracking/write_kernel.h b/src/dwi/tractography/tracking/write_kernel.h index 8f870c2439..db56fbb4bb 100644 --- a/src/dwi/tractography/tracking/write_kernel.h +++ b/src/dwi/tractography/tracking/write_kernel.h @@ -27,6 +27,7 @@ #include "dwi/tractography/properties.h" #include "dwi/tractography/streamline.h" +#include "dwi/tractography/tracking/early_exit.h" #include "dwi/tractography/tracking/generated_track.h" #include "dwi/tractography/tracking/shared.h" #include "dwi/tractography/tracking/types.h" @@ -54,7 +55,8 @@ namespace MR writer (output_file, properties), always_increment (S.properties.seeds.is_finite() || !S.max_num_tracks), warn_on_max_attempts (S.implicit_max_num_attempts), - progress (printf (" 0 generated, 0 selected", 0, 0), always_increment ? S.max_num_attempts : S.max_num_tracks) + progress (printf (" 0 generated, 0 selected", 0, 0), always_increment ? S.max_num_attempts : S.max_num_tracks), + early_exit (shared) { const auto seed_output = properties.find ("seed_output"); if (seed_output != properties.end()) { @@ -93,10 +95,12 @@ namespace MR const bool always_increment, warn_on_max_attempts; std::unique_ptr seeds; ProgressBar progress; + EarlyExit early_exit; }; + } } } From 11a9fe058d5380f84656d417b1849d0f2b908e1f Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Wed, 23 Nov 2016 09:59:20 +0000 Subject: [PATCH 249/723] Eigen MEMALIGN: fix MEMALIGN macro to handle complex template declarations --- lib/types.h | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/types.h b/lib/types.h index 060407659c..1ce476642c 100644 --- a/lib/types.h +++ b/lib/types.h @@ -23,7 +23,6 @@ #include #include -// These lines are to silence deprecation warnings with Eigen & GCC v5 #include /*! \defgroup VLA Variable-length array macros @@ -130,11 +129,11 @@ inline void* __aligned_malloc (std::size_t size) { inline void __aligned_free (void* ptr) { if (ptr) std::free (*(reinterpret_cast(ptr) - 1)); } -#define MEMALIGN(T) public: \ - FORCE_INLINE void* operator new (std::size_t size) { return (alignof(T)>MRTRIX_ALLOC_MEM_ALIGN) ? __aligned_malloc (size) : ::operator new (size); } \ - FORCE_INLINE void* operator new[] (std::size_t size) { return (alignof(T)>MRTRIX_ALLOC_MEM_ALIGN) ? __aligned_malloc (size) : ::operator new[] (size); } \ - FORCE_INLINE void operator delete (void* ptr) { if (alignof(T)>MRTRIX_ALLOC_MEM_ALIGN) __aligned_free (ptr); else ::operator delete (ptr); } \ - FORCE_INLINE void operator delete[] (void* ptr) { if (alignof(T)>MRTRIX_ALLOC_MEM_ALIGN) __aligned_free (ptr); else ::operator delete[] (ptr); } +#define MEMALIGN(...) public: \ + FORCE_INLINE void* operator new (std::size_t size) { return (alignof(__VA_ARGS__)>MRTRIX_ALLOC_MEM_ALIGN) ? __aligned_malloc (size) : ::operator new (size); } \ + FORCE_INLINE void* operator new[] (std::size_t size) { return (alignof(__VA_ARGS__)>MRTRIX_ALLOC_MEM_ALIGN) ? __aligned_malloc (size) : ::operator new[] (size); } \ + FORCE_INLINE void operator delete (void* ptr) { if (alignof(__VA_ARGS__)>MRTRIX_ALLOC_MEM_ALIGN) __aligned_free (ptr); else ::operator delete (ptr); } \ + FORCE_INLINE void operator delete[] (void* ptr) { if (alignof(__VA_ARGS__)>MRTRIX_ALLOC_MEM_ALIGN) __aligned_free (ptr); else ::operator delete[] (ptr); } /*! \def CHECK_MEM_ALIGN @@ -151,8 +150,8 @@ inline void __aligned_free (void* ptr) { if (ptr) std::free (*(reinterpret_cast< * \sa NOMEMALIGN * \sa MEMALIGN */ -#define CHECK_MEM_ALIGN(classname) \ - static_assert ( (alignof(classname) <= MRTRIX_ALLOC_MEM_ALIGN ) || __has_custom_new_operator::value, \ +#define CHECK_MEM_ALIGN(...) \ + static_assert ( (alignof(__VA_ARGS__) <= MRTRIX_ALLOC_MEM_ALIGN ) || __has_custom_new_operator<__VA_ARGS__>::value, \ "class requires over-alignment, but no operator new defined! Please insert MEMALIGN() into class definition.") From e2630d70a484e00bd857415fcc2f88765ceaf684 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Wed, 23 Nov 2016 10:00:00 +0000 Subject: [PATCH 250/723] Eigen MEMALIGN: updated lib/ folder --- lib/adapter/base.h | 2 +- lib/adapter/extract.h | 4 +- lib/adapter/gaussian1D.h | 2 +- lib/adapter/gradient1D.h | 2 +- lib/adapter/gradient3D.h | 2 +- lib/adapter/jacobian.h | 2 +- lib/adapter/median.h | 2 +- lib/adapter/neighbourhood3D.h | 2 +- lib/adapter/normalise3D.h | 2 +- lib/adapter/permute_axes.h | 71 ++++++++++++++-------------- lib/adapter/replicate.h | 3 +- lib/adapter/reslice.h | 3 +- lib/adapter/subset.h | 3 +- lib/adapter/warp.h | 3 +- lib/algo/histogram.h | 10 ++-- lib/algo/iterator.h | 3 +- lib/algo/loop.h | 40 ++++++++-------- lib/algo/min_max.h | 2 +- lib/algo/neighbourhooditerator.h | 2 +- lib/algo/random_loop.h | 6 +-- lib/algo/random_threaded_loop.h | 10 ++-- lib/algo/stochastic_threaded_loop.h | 10 ++-- lib/algo/threaded_copy.h | 2 +- lib/algo/threaded_loop.h | 10 ++-- lib/app.h | 13 ++--- lib/apply.h | 8 ++-- lib/bitset.h | 8 ++-- lib/cmdline_option.h | 11 ++--- lib/datatype.h | 3 +- lib/exception.h | 9 ++-- lib/file/config.h | 3 +- lib/file/dicom/csa_entry.h | 2 +- lib/file/dicom/dict.cpp | 2 +- lib/file/dicom/element.h | 8 ++-- lib/file/dicom/image.h | 4 +- lib/file/dicom/patient.h | 2 +- lib/file/dicom/quick_scan.h | 2 +- lib/file/dicom/series.h | 2 +- lib/file/dicom/study.h | 2 +- lib/file/dicom/tree.h | 2 +- lib/file/entry.h | 3 +- lib/file/gz.h | 3 +- lib/file/key_value.h | 3 +- lib/file/mgh.h | 4 +- lib/file/mmap.h | 3 +- lib/file/name_parser.h | 11 ++--- lib/file/nifti1.h | 14 +++--- lib/file/ofstream.h | 4 +- lib/file/path.h | 3 +- lib/filter/base.h | 3 +- lib/filter/connected_components.h | 8 ++-- lib/filter/dilate.h | 3 +- lib/filter/dwi_brain_mask.h | 3 +- lib/filter/erode.h | 3 +- lib/filter/fft.h | 7 ++- lib/filter/gradient.h | 3 +- lib/filter/mask_clean.h | 3 +- lib/filter/median.h | 3 +- lib/filter/normalise.h | 3 +- lib/filter/optimal_threshold.h | 9 ++-- lib/filter/resize.h | 3 +- lib/filter/smooth.h | 3 +- lib/filter/warp.h | 2 +- lib/formats/list.h | 5 +- lib/hash_map.h | 48 ------------------- lib/header.h | 6 +-- lib/image_helpers.h | 32 ++++++------- lib/image_io/base.h | 2 +- lib/image_io/default.h | 2 +- lib/image_io/gz.h | 2 +- lib/image_io/mosaic.h | 2 +- lib/image_io/pipe.h | 2 +- lib/image_io/ram.h | 2 +- lib/image_io/scratch.h | 2 +- lib/image_io/sparse.h | 2 +- lib/interp/base.h | 2 +- lib/interp/cubic.h | 10 ++-- lib/interp/linear.h | 32 +++++-------- lib/interp/nearest.h | 3 +- lib/interp/sinc.h | 2 +- lib/math/SH.h | 10 ++-- lib/math/Sn_scale_estimator.h | 2 +- lib/math/constrained_least_squares.h | 4 +- lib/math/cubic_spline.h | 6 +-- lib/math/gradient_descent.h | 4 +- lib/math/gradient_descent_bb.h | 4 +- lib/math/hermite.h | 5 +- lib/math/quadratic_line_search.h | 26 +++++----- lib/math/rng.h | 8 ++-- lib/math/sinc.h | 2 +- lib/math/stats/glm.h | 3 +- lib/math/versor.h | 4 +- lib/memory.h | 6 ++- lib/mrtrix.h | 8 ++-- lib/progressbar.h | 6 +-- lib/sparse/fixel_metric.h | 3 +- lib/sparse/image.h | 4 +- lib/stats.h | 8 ++-- lib/stride.cpp | 2 +- lib/stride.h | 8 ++-- lib/thread.h | 14 +++--- lib/thread_queue.h | 51 +++++++------------- lib/timer.h | 8 ++-- lib/transform.h | 3 +- lib/types.h | 7 +-- testing/cmd/testing_gen_data.cpp | 2 +- 106 files changed, 308 insertions(+), 439 deletions(-) delete mode 100644 lib/hash_map.h diff --git a/lib/adapter/base.h b/lib/adapter/base.h index 39be6ee565..f42d1896bd 100644 --- a/lib/adapter/base.h +++ b/lib/adapter/base.h @@ -31,7 +31,7 @@ namespace MR } template - class Base { + class Base { MEMALIGN(Base) public: Base (const ImageType& parent) : parent_ (parent) { diff --git a/lib/adapter/extract.h b/lib/adapter/extract.h index 48149eadc9..b56d99cbb9 100644 --- a/lib/adapter/extract.h +++ b/lib/adapter/extract.h @@ -24,7 +24,7 @@ namespace MR { template class Extract1D : public Base - { + { MEMALIGN (Extract1D) public: using Base::ndim; using Base::spacing; @@ -104,7 +104,7 @@ namespace MR template class Extract : public Base - { + { MEMALIGN (Extract) public: using Base::ndim; using Base::spacing; diff --git a/lib/adapter/gaussian1D.h b/lib/adapter/gaussian1D.h index 719ebe9071..024a99f3c1 100644 --- a/lib/adapter/gaussian1D.h +++ b/lib/adapter/gaussian1D.h @@ -24,7 +24,7 @@ namespace MR { template - class Gaussian1D : public Base { + class Gaussian1D : public Base { MEMALIGN (Gaussian1D) public: Gaussian1D (const ImageType& parent, default_type stdev_in = 1.0, diff --git a/lib/adapter/gradient1D.h b/lib/adapter/gradient1D.h index 6fe1986bda..b74b6c70ad 100644 --- a/lib/adapter/gradient1D.h +++ b/lib/adapter/gradient1D.h @@ -24,7 +24,7 @@ namespace MR { template - class Gradient1D : public Base { + class Gradient1D : public Base { MEMALIGN(Gradient1D) public: typedef typename ImageType::value_type value_type; diff --git a/lib/adapter/gradient3D.h b/lib/adapter/gradient3D.h index 9a8f1088b1..b515ddb504 100644 --- a/lib/adapter/gradient3D.h +++ b/lib/adapter/gradient3D.h @@ -32,7 +32,7 @@ namespace MR { template - class Gradient3D : public Gradient1D { + class Gradient3D : public Gradient1D { MEMALIGN(Gradient3D) public: typedef typename ImageType::value_type value_type; diff --git a/lib/adapter/jacobian.h b/lib/adapter/jacobian.h index c1e9a88a71..66a924d643 100644 --- a/lib/adapter/jacobian.h +++ b/lib/adapter/jacobian.h @@ -26,7 +26,7 @@ namespace MR { template - class Jacobian : public Base { + class Jacobian : public Base { MEMALIGN (Jacobian) public: Jacobian (const WarpType& parent, bool wrt_scanner = true) : diff --git a/lib/adapter/median.h b/lib/adapter/median.h index aff7bfdad6..b39698a44e 100644 --- a/lib/adapter/median.h +++ b/lib/adapter/median.h @@ -26,7 +26,7 @@ namespace MR template - class Median : public Base { + class Median : public Base { MEMALIGN(Median) public: Median (const ImageType& parent) : Base (parent) { diff --git a/lib/adapter/neighbourhood3D.h b/lib/adapter/neighbourhood3D.h index 3a7785c7e8..3d8cc24ff3 100644 --- a/lib/adapter/neighbourhood3D.h +++ b/lib/adapter/neighbourhood3D.h @@ -32,7 +32,7 @@ namespace MR template class NeighbourhoodCoord : public Base - { + { MEMALIGN (NeighbourhoodCoord) public: typedef typename ImageType::value_type value_type; diff --git a/lib/adapter/normalise3D.h b/lib/adapter/normalise3D.h index 2acffa4ac7..dcbccb78ec 100644 --- a/lib/adapter/normalise3D.h +++ b/lib/adapter/normalise3D.h @@ -25,7 +25,7 @@ namespace MR template - class Normalise3D : public Base { + class Normalise3D : public Base { MEMALIGN(Normalise3D) public: Normalise3D (const ImageType& parent) : Base (parent) { diff --git a/lib/adapter/permute_axes.h b/lib/adapter/permute_axes.h index b3647bb46f..dd5bf92e52 100644 --- a/lib/adapter/permute_axes.h +++ b/lib/adapter/permute_axes.h @@ -24,50 +24,49 @@ namespace MR { template - class PermuteAxes : public Base - { - public: - using Base::size; - using Base::parent; - typedef typename ImageType::value_type value_type; + class PermuteAxes : public Base { MEMALIGN (PermuteAxes) + public: + using Base::size; + using Base::parent; + typedef typename ImageType::value_type value_type; - PermuteAxes (const ImageType& original, const std::vector& axes) : - Base (original), - axes_ (axes) { - for (int i = 0; i < static_cast (parent().ndim()); ++i) { - for (size_t a = 0; a < axes_.size(); ++a) - if (axes_[a] == i) - goto next_axis; - if (parent().size (i) != 1) - throw Exception ("omitted axis \"" + str (i) + "\" has dimension greater than 1"); + PermuteAxes (const ImageType& original, const std::vector& axes) : + Base (original), + axes_ (axes) { + for (int i = 0; i < static_cast (parent().ndim()); ++i) { + for (size_t a = 0; a < axes_.size(); ++a) + if (axes_[a] == i) + goto next_axis; + if (parent().size (i) != 1) + throw Exception ("omitted axis \"" + str (i) + "\" has dimension greater than 1"); next_axis: - continue; + continue; + } } - } - size_t ndim () const { - return axes_.size(); - } - ssize_t size (size_t axis) const { - return axes_[axis] < 0 ? 1 : parent().size (axes_[axis]); - } - default_type spacing (size_t axis) const { - return axes_[axis] < 0 ? std::numeric_limits::quiet_NaN() : parent().spacing (axes_[axis]); - } - ssize_t stride (size_t axis) const { - return axes_[axis] < 0 ? 0 : parent().stride (axes_[axis]); - } + size_t ndim () const { + return axes_.size(); + } + ssize_t size (size_t axis) const { + return axes_[axis] < 0 ? 1 : parent().size (axes_[axis]); + } + default_type spacing (size_t axis) const { + return axes_[axis] < 0 ? std::numeric_limits::quiet_NaN() : parent().spacing (axes_[axis]); + } + ssize_t stride (size_t axis) const { + return axes_[axis] < 0 ? 0 : parent().stride (axes_[axis]); + } - void reset () { parent().reset(); } + void reset () { parent().reset(); } - ssize_t index (size_t axis) const { return axes_[axis] < 0 ? 0 : parent().index (axes_[axis]); } - auto index (size_t axis) -> decltype(Helper::index(*this, axis)) { return { *this, axis }; } - void move_index (size_t axis, ssize_t increment) { parent().index (axes_[axis]) += increment; } + ssize_t index (size_t axis) const { return axes_[axis] < 0 ? 0 : parent().index (axes_[axis]); } + auto index (size_t axis) -> decltype(Helper::index(*this, axis)) { return { *this, axis }; } + void move_index (size_t axis, ssize_t increment) { parent().index (axes_[axis]) += increment; } - private: - const std::vector axes_; + private: + const std::vector axes_; - }; + }; } } diff --git a/lib/adapter/replicate.h b/lib/adapter/replicate.h index 5b37981672..c70937a194 100644 --- a/lib/adapter/replicate.h +++ b/lib/adapter/replicate.h @@ -26,8 +26,7 @@ namespace MR { template - class Replicate : public Base - { + class Replicate : public Base { MEMALIGN(Replicate) public: typedef typename ImageType::value_type value_type; diff --git a/lib/adapter/reslice.h b/lib/adapter/reslice.h index 3afa76153c..65d19129c3 100644 --- a/lib/adapter/reslice.h +++ b/lib/adapter/reslice.h @@ -71,8 +71,7 @@ namespace MR * \sa Interp::reslice() */ template