From 0f2c63c376fce75202f44d48762c72c4bce3c461 Mon Sep 17 00:00:00 2001 From: Ben Jeurissen Date: Thu, 1 Oct 2015 15:08:57 +0200 Subject: [PATCH 001/139] initial commit --- cmd/mrgrc.cpp | 301 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 cmd/mrgrc.cpp diff --git a/cmd/mrgrc.cpp b/cmd/mrgrc.cpp new file mode 100644 index 0000000000..c507c5f483 --- /dev/null +++ b/cmd/mrgrc.cpp @@ -0,0 +1,301 @@ +#include "command.h" +#include "progressbar.h" +#include "image.h" +#include "algo/threaded_loop.h" +#include +#include + +using namespace MR; +using namespace App; + +void usage () +{ + DESCRIPTION + + "Remove Gibbs Ringing Artifact"; + + ARGUMENTS + + Argument ("in", "the input image.").type_image_in () + + Argument ("out", "the output image.").type_image_out (); + + + OPTIONS + + Option ("axes", + "select the slice axes (default: 0,1 - i.e. x-y)") + + Argument ("list").type_sequence_int (); +} + + +constexpr double pi = 3.1416; +typedef double value_type; + + +void unring_1d(fftw_complex* data, size_t n, size_t numlines, size_t nsh, size_t minW, size_t maxW) { + fftw_complex* in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n); + fftw_complex* out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n); + fftw_plan p = fftw_plan_dft_1d(n, in, out, FFTW_FORWARD , FFTW_ESTIMATE); + fftw_plan pinv = fftw_plan_dft_1d(n, in, out, FFTW_BACKWARD, FFTW_ESTIMATE); + fftw_complex* sh = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n*(2*nsh+1)); + fftw_complex* sh2 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n*(2*nsh+1)); + double nfac = 1/double(n); + size_t* shifts = (size_t*) malloc(sizeof(size_t)*(2*nsh+1)); + shifts[0] = 0; + for (size_t j = 0; j < nsh; j++) + { + shifts[j+1] = j+1; + shifts[1+nsh+j] = -(j+1); + } + + double TV1arr[2*nsh+1]; + double TV2arr[2*nsh+1]; + + for (size_t k = 0; k < numlines; k++) + { + fftw_execute_dft(p, &(data[n*k]), sh); + size_t maxn = (n%2==1) ? (n-1)/2 : n/2-1; + + for (size_t j = 1; j < 2*nsh+1; j++) + { + double phi = pi/double(n) * double(shifts[j])/double(nsh); + fftw_complex u = {cos(phi),sin(phi)}; + fftw_complex e = {1,0}; + sh[j*n ][0] = sh[0][0]; + sh[j*n ][1] = sh[0][1]; + + if (n%2 == 0) + { + sh[j*n + n/2][0] = 0; + sh[j*n + n/2][1] = 0; + } + + for (size_t l = 0; l < maxn; l++) + { + double tmp = e[0]; + e[0] = u[0]*e[0] - u[1]*e[1]; + e[1] = tmp*u[1] + u[0]*e[1]; + + size_t L = l+1; + sh[j*n +L][0] = (e[0]*sh[L][0] - e[1]*sh[L][1]); + sh[j*n +L][1] = (e[0]*sh[L][1] + e[1]*sh[L][0]); + L = n-1-l; + sh[j*n +L][0] = (e[0]*sh[L][0] + e[1]*sh[L][1]); + sh[j*n +L][1] = (e[0]*sh[L][1] - e[1]*sh[L][0]); + + } + } + + + for (size_t j = 0; j < 2*nsh+1; j++) + { + fftw_execute_dft(pinv, &(sh[j*n]), &sh2[j*n]); + } + + for (size_t j=0; j < 2*nsh+1; j++) + { + TV1arr[j] = 0; + TV2arr[j] = 0; + const size_t l = 0; + for (size_t t = minW; t <= maxW; t++) + { + TV1arr[j] += fabs(sh2[j*n + (l-t+n)%n ][0] - sh2[j*n + (l-(t+1)+n)%n ][0]); + TV1arr[j] += fabs(sh2[j*n + (l-t+n)%n ][1] - sh2[j*n + (l-(t+1)+n)%n ][1]); + TV2arr[j] += fabs(sh2[j*n + (l+t+n)%n ][0] - sh2[j*n + (l+(t+1)+n)%n ][0]); + TV2arr[j] += fabs(sh2[j*n + (l+t+n)%n ][1] - sh2[j*n + (l+(t+1)+n)%n ][1]); + } + } + + for(size_t l=0; l < n; l++) + { + double minTV = 999999999999; + size_t minidx= 0; + for (size_t j=0;j < 2*nsh+1; j++) + { + + if (TV1arr[j] < minTV) + { + minTV = TV1arr[j]; + minidx = j; + } + if (TV2arr[j] < minTV) + { + minTV = TV2arr[j]; + minidx = j; + } + + TV1arr[j] += fabs(sh2[j*n + (l-minW+1+n)%n ][0] - sh2[j*n + (l-(minW)+n)%n ][0]); + TV1arr[j] -= fabs(sh2[j*n + (l-maxW+n)%n ][0] - sh2[j*n + (l-(maxW+1)+n)%n ][0]); + TV2arr[j] += fabs(sh2[j*n + (l+maxW+1+n)%n ][0] - sh2[j*n + (l+(maxW+2)+n)%n ][0]); + TV2arr[j] -= fabs(sh2[j*n + (l+minW+n)%n ][0] - sh2[j*n + (l+(minW+1)+n)%n ][0]); + + TV1arr[j] += fabs(sh2[j*n + (l-minW+1+n)%n ][1] - sh2[j*n + (l-(minW)+n)%n ][1]); + TV1arr[j] -= fabs(sh2[j*n + (l-maxW+n)%n ][1] - sh2[j*n + (l-(maxW+1)+n)%n ][1]); + TV2arr[j] += fabs(sh2[j*n + (l+maxW+1+n)%n ][1] - sh2[j*n + (l+(maxW+2)+n)%n ][1]); + TV2arr[j] -= fabs(sh2[j*n + (l+minW+n)%n ][1] - sh2[j*n + (l+(minW+1)+n)%n ][1]); + } + + double a0r = sh2[minidx*n + (l-1+n)%n ][0]; + double a1r = sh2[minidx*n + l][0]; + double a2r = sh2[minidx*n + (l+1+n)%n ][0]; + double a0i = sh2[minidx*n + (l-1+n)%n ][1]; + double a1i = sh2[minidx*n + l][1]; + double a2i = sh2[minidx*n + (l+1+n)%n ][1]; + double s = double(shifts[minidx])/nsh/2; + + if (s>0) + { + data[k*n + l][0] = (a1r*(1-s) + a0r*s)*nfac; + data[k*n + l][1] = (a1i*(1-s) + a0i*s)*nfac; + } + else + { + s = -s; + data[k*n + l][0] = (a1r*(1-s) + a2r*s)*nfac; + data[k*n + l][1] = (a1i*(1-s) + a2i*s)*nfac; + } + } + } + free(shifts); + fftw_destroy_plan(p); + fftw_destroy_plan(pinv); + fftw_free(in); + fftw_free(out); + fftw_free(sh); + fftw_free(sh2); +} + + +void unring_2d(fftw_complex* data1,fftw_complex* tmp2, const size_t* dim_sz, size_t nsh, size_t minW, size_t maxW) { + fftw_complex* tmp1 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * dim_sz[0]*dim_sz[1]); + fftw_complex* data2 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * dim_sz[0]*dim_sz[1]); + + fftw_plan p = fftw_plan_dft_2d(dim_sz[1],dim_sz[0], data1, tmp1, FFTW_FORWARD, FFTW_ESTIMATE); + fftw_plan pinv = fftw_plan_dft_2d(dim_sz[1],dim_sz[0], data1, tmp1, FFTW_BACKWARD, FFTW_ESTIMATE); + fftw_plan p_tr = fftw_plan_dft_2d(dim_sz[0],dim_sz[1], data2, tmp2, FFTW_FORWARD, FFTW_ESTIMATE); + fftw_plan pinv_tr = fftw_plan_dft_2d(dim_sz[0],dim_sz[1], data2, tmp2, FFTW_BACKWARD, FFTW_ESTIMATE); + double nfac = 1/double(dim_sz[0]*dim_sz[1]); + + for (size_t k = 0; k < dim_sz[1]; k++) + { + for (size_t j = 0; j < dim_sz[0]; j++) + { + data2[j*dim_sz[1]+k][0] = data1[k*dim_sz[0]+j][0]; + data2[j*dim_sz[1]+k][1] = data1[k*dim_sz[0]+j][1]; + } + } + + fftw_execute_dft(p,data1,tmp1); + fftw_execute_dft(p_tr,data2,tmp2); + + for (size_t k = 0; k < dim_sz[1]; k++) + { + double ck = (1+cos(2*pi*(double(k)/dim_sz[1])))*0.5; + for (size_t j = 0 ; j < dim_sz[0]; j++) + { + double cj = (1+cos(2*pi*(double(j)/dim_sz[0])))*0.5; + tmp1[k*dim_sz[0]+j][0] = nfac*(tmp1[k*dim_sz[0]+j][0] * ck) / (ck+cj); + tmp1[k*dim_sz[0]+j][1] = nfac*(tmp1[k*dim_sz[0]+j][1] * ck) / (ck+cj); + tmp2[j*dim_sz[1]+k][0] = nfac*(tmp2[j*dim_sz[1]+k][0] * cj) / (ck+cj); + tmp2[j*dim_sz[1]+k][1] = nfac*(tmp2[j*dim_sz[1]+k][1] * cj) / (ck+cj); + } + } + + fftw_execute_dft(pinv,tmp1,data1); + fftw_execute_dft(pinv_tr,tmp2,data2); + + unring_1d(data1,dim_sz[0],dim_sz[1],nsh,minW,maxW); + unring_1d(data2,dim_sz[1],dim_sz[0],nsh,minW,maxW); + + fftw_execute_dft(p,data1,tmp1); + fftw_execute_dft(p_tr,data2,tmp2); + + for (size_t k = 0; k < dim_sz[1]; k++) + { + for (size_t j = 0; j < dim_sz[0]; j++) + { + tmp1[k*dim_sz[0]+j][0] = nfac*(tmp1[k*dim_sz[0]+j][0] + tmp2[j*dim_sz[1]+k][0]); + tmp1[k*dim_sz[0]+j][1] = nfac*(tmp1[k*dim_sz[0]+j][1] + tmp2[j*dim_sz[1]+k][1]); + } + } + + fftw_execute_dft(pinv,tmp1,tmp2); + + fftw_free(data2); + fftw_free(tmp1); +} + +class ComputeSlice +{ + public: + ComputeSlice (const std::vector& outer_axes, const std::vector& slice_axes, const size_t& nsh, const size_t& minW, const size_t& maxW, Image& in, Image& out) : + outer_axes (outer_axes), + slice_axes (slice_axes), + nsh (nsh), + minW (minW), + maxW (maxW), + in (in), + out (out) { } + + void operator() (const Iterator& pos) + { + assign_pos_of (pos, outer_axes).to (in, out); + size_t dims[2]; + dims[0] = in.size(slice_axes[0]); + dims[1] = in.size(slice_axes[1]); + fftw_complex* in_complex = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*dims[0]*dims[1]); + size_t i = 0; + for (auto l = Loop (slice_axes) (in); l; ++l) + { + in_complex[i][0] = in.value(); + in_complex[i++][1] = 0; + } + fftw_complex* out_complex = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*dims[0]*dims[1]); + unring_2d(in_complex,out_complex,dims,nsh,minW,maxW); + fftw_free(in_complex); + i = 0; + for (auto l = Loop (slice_axes) (out); l; ++l) + { + out.value() = out_complex[i++][0]; + } + fftw_free(out_complex); + } + + private: + const std::vector& outer_axes; + const std::vector& slice_axes; + const size_t& nsh; + const size_t& minW; + const size_t& maxW; + Image in, out; +}; + +void run () +{ + auto in = Image::open (argument[0]); + auto header = in.header(); + + header.datatype() = DataType::Float64; + auto out = Image::create (argument[1], header); + + std::vector slice_axes = { 0, 1 }; + auto opt = get_options ("axes"); + if (opt.size()) { + std::vector axes = opt[0][0]; + if (slice_axes.size() != 2) + throw Exception ("slice axes must be specified as a comma-separated 2-vector"); + slice_axes = { size_t(axes[0]), size_t(axes[1]) }; + } + + // build vector of outer axes: + std::vector outer_axes (header.ndim()); + std::iota (outer_axes.begin(), outer_axes.end(), 0); + for (const auto axis : slice_axes) + { + auto it = std::find (outer_axes.begin(), outer_axes.end(), axis); + if (it == outer_axes.end()) + throw Exception ("slice axis out of range!"); + outer_axes.erase (it); + } + + ThreadedLoop ("computing stuff...", in, outer_axes, slice_axes) + .run_outer (ComputeSlice (outer_axes, slice_axes, 30, 1, 3, in, out)); +} + From bf14e02c840bfb02cbb58aceb9a680e87902c6e3 Mon Sep 17 00:00:00 2001 From: Ben Jeurissen Date: Thu, 1 Oct 2015 16:01:29 +0200 Subject: [PATCH 002/139] added configure --- configure | 1232 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1232 insertions(+) create mode 100755 configure diff --git a/configure b/configure new file mode 100755 index 0000000000..d332791a6d --- /dev/null +++ b/configure @@ -0,0 +1,1232 @@ +#!/usr/bin/env python + +import subprocess, sys, os, platform, tempfile, shutil, shlex, re, copy + + +debug = False +asserts = False +profile = False +nogui = False +noshared = False +static = False +verbose = False +R_module = False +profile_name = None +sh_basis_def = None + +for arg in sys.argv[1:]: + if '-debug'.startswith (arg): debug = True + elif '-assert'.startswith (arg): asserts = True + elif '-profile'.startswith (arg): profile = True + elif '-nogui'.startswith (arg): nogui = True + elif '-noortho'.startswith (arg): sh_basis_def = '-DUSE_NON_ORTHONORMAL_SH_BASIS' + elif '-noshared'.startswith (arg): noshared = True + elif '-static'.startswith (arg): + static = True + noshared = True + elif '-verbose'.startswith (arg): verbose = True + elif '-R'.startswith (arg): + R_module = True + #noshared = True + nogui = True + elif arg[0] != '-': + if profile_name != None: + print ('configure: too many names supplied') + sys.exit (1) + profile_name = arg + else: + print (""" +usage: [ENV] ./configure [name] [-debug] [-assert] [-profile] [-nogui] [-noshared] + +In most cases, a simple invocation should work: + + $ ./configure + +By default, this creates a target folder named 'release', and a 'config' file +within that folder. All intermediate files will be placed within the target +folder to avoid conflict with any other configurations. If a name is provided, +the configuration will be written to a different folder, which can then be used +by the build script. For example: + + $ ./configure testing -debug -assert + +will generate the folder 'testing' and a config file with debugging symbols and +assertions enabled, which can be used with the build script as follows: + + $ ./build testing + +By default, the build script will compile the 'release' folder. + +Note that naming a target folder does *not* set any compiler flags. These are +modified by command-line options or environment variables. It is perfectly +valid to use non-standard options with a default ('release') target folder, or +standard options in a named target folder. For instance: + + $ ARCH=x86-84 ./configure -noshared + +will produce a 'release' (the default name) target folder with non-default +compiler options, while: + + $ ./configure normal + +will produce a target folder named 'normal' using default compiler settings. + + +OPTIONS: + + -debug enable debugging symbols. + + -assert enable all assert() and related checks. + + -profile enable profiling. + + -nogui disable GUI components. + + -noshared disable shared library generation. + + -R used to generate an R module (implies -noshared) + + -static produce statically-linked executables. + + -verbose enable more informative output. + + +ENVIRONMENT VARIABLES: + +For non-standard setups, you may need to supply additional +information using environment variables. For example, to set +the compiler, use: + $ CXX=/usr/local/bin/g++-4.1 ./configure + +The following environment variables can be set: + +CXX The compiler command to use. The default is "g++" ("clang" on MacOSX) + +CXX_ARGS The arguments expected by the compiler. The default is: + "-c CFLAGS SRC -o OBJECT" + +LD The linker command to use. The default is the same as CXX. + +LD_ARGS The arguments expected by the linker. The default is: + "LDFLAGS OBJECTS -o EXECUTABLE" + +LDLIB_ARGS The arguments expected by the linker for generating a shared library. + The default is: + "-shared LDLIB_FLAGS OBJECTS -o LIB" + +ARCH the specific CPU architecture to compile for. This variable + will be passed to the compiler using -march=$ARCH. + The default is 'native'. + +CFLAGS Any additional flags to the compiler. + +LDFLAGS Any additional flags to the linker. + +LDLIB_FLAGS Any additional flags to the linker to generate a shared library. + +GSL_CFLAGS Any flags required to compile with the GSL. + This may include in particular the path to the + include files, if not in a standard location + For example: + $ GSL_CFLAGS="-I/usr/local/include" ./configure + +GSL_LDFLAGS Any flags required to link with the GSL. + This may include in particular the path to the + libraries, if not in a standard location + For example: + $ GSL_LDFLAGS="-L/usr/local/lib -lgsl -lgslcblas" ./configure + +ZLIB_CFLAGS Any flags required to compile with the zlib compression library. + +ZLIB_LDFLAGS Any flags required to link with the zlib compression library. + +CBLAS_LDFLAGS Any flags required to link with an alternate cblas library. + +QMAKE The command to run to invoke qmake. + +MOC The command to invoke Qt's meta-object compile (default: moc) + +RCC The command to invoke Qt's resource compiler (default: rcc) + +PATH Set the path to use during the configure process. + This may be useful to set the path to Qt's qmake. + For example: + $ PATH=/usr/local/bin:$PATH ./configure + + Note that this path will NOT be used during the build + process itself. +""") + sys.exit (0) + + +if not profile_name: + profile_name = 'release' + + +try: + os.makedirs (profile_name) +except OSError: + if not os.path.isdir (profile_name): + raise + + +global logfile, config_report +logfile = open (os.path.join (os.path.dirname(sys.argv[0]), 'configure.log'), 'wb') +config_report = '' + + +def log (message): + global logfile + logfile.write (message.encode (errors='ignore')) + if (verbose): + sys.stdout.write (message) + sys.stdout.flush() + +def report (message): + global config_report, logfile + config_report += message + sys.stdout.write (message) + sys.stdout.flush() + logfile.write (('\nREPORT: ' + message.rstrip() + '\n').encode (errors='ignore')) + +def error (message): + global logfile + logfile.write (('\nERROR: ' + message.rstrip() + '\n\n').encode (errors='ignore')) + sys.stderr.write ('\nERROR: ' + message.rstrip() + '\n\n') + sys.exit (1) + + +report (""" +MRtrix build type requested: """) +if profile: report ('profiling') +elif debug: report ('debug') +else: report ('release') +if asserts: report (' with asserts') +if nogui: report (' [command-line only]') +report ('\n\n') + + + + +global cpp, cpp_cmd, ld, ld_args, ld_cmd + +cxx = [ 'g++' ] +cxx_args = '-c CFLAGS SRC -o OBJECT'.split() +cpp_flags = [ '-std=c++11' ] + +ld_args = 'OBJECTS LDFLAGS -o EXECUTABLE'.split() +ld_flags = [ ] + +if static: + ld_flags += [ '-static', '-Wl,-u,pthread_cancel,-u,pthread_cond_broadcast,-u,pthread_cond_destroy,-u,pthread_cond_signal,-u,pthread_cond_wait,-u,pthread_create,-u,pthread_detach,-u,pthread_cond_signal,-u,pthread_equal,-u,pthread_join,-u,pthread_mutex_lock,-u,pthread_mutex_unlock,-u,pthread_once,-u,pthread_setcancelstate' ] + +ld_lib_args = 'OBJECTS LDLIB_FLAGS -o LIB'.split() +ld_lib_flags = [ ] + +zlib_cflags = [] +zlib_ldflags = [ '-lz' ] + +gsl_cflags = [] +gsl_ldflags = [ '-lgsl', '-lgslcblas' ] + +fftw3_cflags = [] +fftw3_ldflags = [ '-lfftw3' ] + +class TempFile: + def __init__ (self, suffix): + self.fid = None + self.name = None + [ fid, self.name ] = tempfile.mkstemp (suffix) + self.fid = os.fdopen (fid, 'w') + + def __enter__ (self): + return self + + def __exit__(self, type, value, traceback): + try: + os.unlink (self.name) + except OSError as error: + log ('error deleting temporary file "' + self.name + '": ' + error.strerror) + except: + raise + + + +class DeleteAfter: + def __init__ (self, name): + self.name = name + + def __enter__ (self): + return self + + def __exit__(self, exception_type, value, traceback): + try: + os.unlink (self.name) + except OSError as error: + log ('error deleting temporary file "' + self.name + '": ' + error.strerror) + except: + raise + + +class TempDir: + def __init__ (self): + self.name = tempfile.mkdtemp (); + + def __enter__ (self): + return self + + def __exit__(self, type, value, traceback): + try: + for entry in os.listdir (self.name): + fname = os.path.join (self.name, entry) + if os.path.isdir (fname): + os.rmdir (fname) + else: + os.unlink (fname) + os.rmdir (self.name) + + except OSError as error: + log ('error deleting temporary folder "' + self.name + '": ' + error.strerror) + except: + raise + + + +class QMakeError (Exception): pass +class QMOCError (Exception): pass +class CompileError (Exception): pass +class LinkError (Exception): pass +class RuntimeError (Exception): pass + +def commit (name, variable): + cache.write (name + ' = ') + if type (variable) == type([]): + cache.write ('[') + if len(variable): cache.write(' \'' + '\', \''.join (variable) + '\' ') + cache.write (']\n') + else: cache.write ('\'' + variable + '\'\n') + + + +def fillin (template, keyvalue): + cmd = [] + for item in template: + if item in keyvalue: + if type(keyvalue[item]) == type ([]): cmd += keyvalue[item] + else: cmd += [ keyvalue[item] ] + else: cmd += [ item ] + return cmd + + + +def execute (cmd, exception, raise_on_non_zero_exit_code = True, cwd = None): + log ('EXEC <<\nCMD: ' + ' '.join(cmd) + '\n') + try: + process = subprocess.Popen (cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) + ( stdout, stderr ) = process.communicate() + + log ('EXIT: ' + str(process.returncode) + '\n') + stdout = stdout.decode(errors='ignore').rstrip() + if len (stdout): log ('STDOUT:\n' + stdout + '\n') + stderr = stderr.decode(errors='ignore').rstrip() + if len (stderr): log ('STDERR:\n' + stderr + '\n') + log ('>>\n\n') + + except OSError as error: + log ('error invoking command "' + cmd[0] + '": ' + error.strerror + '\n>>\n\n') + raise exception + except: + print ('Unexpected error:', str(sys.exc_info())) + raise + else: + if raise_on_non_zero_exit_code and process.returncode != 0: + raise exception (stderr) + + + return (process.returncode, stdout, stderr) + + + +def compile (source, compiler_flags = [], linker_flags = []): + global cpp, ld + with TempFile ('.cpp') as F: + log ('\nCOMPILE ' + F.name + ':\n---\n' + source + '\n---\n') + F.fid.write (source) + F.fid.flush() + F.fid.close() + with DeleteAfter (F.name[:-4] + '.o') as obj: + cmd = fillin (cpp, { + 'CFLAGS': compiler_flags, + 'SRC': F.name, + 'OBJECT': obj.name }) + execute (cmd, CompileError) + + with DeleteAfter ('a.out') as out: + cmd = fillin (ld, { + 'LDFLAGS': linker_flags, + 'OBJECTS': obj.name, + 'EXECUTABLE': out.name }) + execute (cmd, LinkError) + + ret = execute ([ './'+out.name ], RuntimeError) + + return ret[1] + + +def compare_version (needed, observed): + needed = [ float(n) for n in needed.split()[0].split('.') ] + observed = [ float(n) for n in observed.split()[0].split('.') ] + for n in zip (needed, observed): + if n[0] > n[1]: + return False + return True + + + + + + + + +# OS-dependent variables: + +obj_suffix = '.o' +exe_suffix = '' +lib_prefix = 'lib' + +system = platform.system().lower() +report ('Detecting OS: ' + system + '\n') +if system == 'linux': + cpp_flags += [ '-pthread', '-fPIC' ] + lib_suffix = '.so' + ld_flags += [ '-pthread' ] + ld_lib_flags += [ '-pthread', '-shared' ] + runpath = '-Wl,-rpath,$ORIGIN/' +elif system == 'windows': + cpp_flags += [ '-pthread', '-DMRTRIX_WINDOWS', '-mms-bitfields' ] + exe_suffix = '.exe' + lib_prefix = '' + lib_suffix = '.dll' + ld_flags += [ '-pthread', '-Wl,--allow-multiple-definition' ] + ld_lib_flags += [ '-pthread', '-shared' ] + runpath = '' +elif system == 'darwin': + cxx = [ 'clang++' ] + cpp_flags += [ '-DMRTRIX_MACOSX', '-fPIC' ] + ld_lib_flags += [ '-dynamiclib', '-install_name', '@rpath/LIBNAME' ] + runpath = '-Wl,-rpath,@loader_path/' + lib_suffix = '.dylib' + + + + +if 'ARCH' in os.environ.keys(): + march = os.environ['ARCH'] + report ('Machine architecture set by ARCH environment variable to: ' + march + '\n') + cpp_flags += [ '-march='+march ] +else: + if system != 'darwin': + cpp_flags += [ '-march=native' ] + + + + +# set CPP compiler: + +if 'CXX' in os.environ.keys(): cxx = shlex.split (os.environ['CXX']) +if 'CXX_ARGS' in os.environ.keys(): cxx_args = shlex.split (os.environ['CXX_ARGS']) +ld = copy.copy(cxx) +if 'LD' in os.environ.keys(): ld = shlex.split (os.environ['LD']) +if 'LD_ARGS' in os.environ.keys(): ld_args = shlex.split (os.environ['LD_ARGS']) +if 'LDLIB_ARGS' in os.environ.keys(): ld_lib_args = shlex.split (os.environ['LDLIB_ARGS']) + +cpp = cxx + cxx_args +ld_lib = ld + ld_lib_args +ld += ld_args + + + + +# CPP flags: + +if 'CFLAGS' in os.environ.keys(): cpp_flags += shlex.split (os.environ['CFLAGS']) +if 'LDFLAGS' in os.environ.keys(): ld_flags + shlex.split (os.environ['LDFLAGS']) +ld_lib_flags += ld_flags +if 'LDLIB_FLAGS' in os.environ.keys(): ld_lib_flags + shlex.split (os.environ['LDLIB_FLAGS']) + + + +report ('Checking for C++11 compliant compiler [' + cpp[0] + ']: ') +try: + compiler_version = execute ([ cpp[0], '-dumpversion' ], CompileError)[1] + if len(compiler_version) == 0: report ('(no version information)') + else: report (compiler_version) +except: + error ('''compiler not found! + +Use CXX environment variable to set path to compiler, as follows: + CXX=/usr/bin/g++-4.8 ./configure''') + +try: + compile (''' +struct Base { + Base (int); +}; +struct Derived : Base { + using Base::Base; +}; + +int main() { + Derived D (int); // check for contructor inheritance + return (0); +} +''', cpp_flags, ld_flags) + report (' - tested ok\n') +except CompileError: + error ('''compiler test failed! + +Use CXX environment variable to set path to compiler, as follows: + CXX=/usr/bin/g++-4.8 ./configure + +You can also use the CXX_ARGS to set the arguments expected by the +compiler, in case this differs from gcc, as follows: + + CXX_ARGS="-c CFLAGS SRC -o OBJECT" ./configure''') +except (LinkError, RuntimeError): + error ('''linking error! + +Use LD environment variable to set path to compiler, as follows: + LD=/usr/bin/g++-4.8 ./configure''') +except: + raise + + + + + + +report ('Detecting pointer size: ') +try: + pointer_size = int (compile (''' +#include +int main() { + std::cout << sizeof(void*); + return (0); +} +''', cpp_flags, ld_flags)) + report (str(8*pointer_size) + ' bit\n') + if pointer_size == 8: cpp_flags += [ '-DMRTRIX_WORD64' ] + elif pointer_size != 4: + error ('unexpected pointer size!') +except: + error ('unable to determine pointer size!') + + + + +report ('Detecting byte order: ') +if sys.byteorder == 'big': + report ('big-endian\n') + cpp_flags += [ '-DMRTRIX_BYTE_ORDER_IS_BIG_ENDIAN' ] +else: + report ('little-endian\n') + + + + + + + +report ('Checking for variable-length array support: ') +try: + compile (''' + +int main(int argc, char* argv[]) { + int x[argc]; + return 0; +} +''', cpp_flags, ld_flags) + report ('yes\n') +except: + report ('no\n') + cpp_flags += [ '-DMRTRIX_NO_VLA' ] + + + +report ('Checking for non-POD variable-length array support: ') +try: + compile (''' +#include + +class X { + int x; + double y; + std::string s; +}; + +int main(int argc, char* argv[]) { + X x[argc]; + return 0; +} +''', cpp_flags, ld_flags) + report ('yes\n') +except: + report ('no\n') + cpp_flags += [ '-DMRTRIX_NO_NON_POD_VLA' ] + + + + +# zlib: + +report ('Checking for zlib compression library: ') + +if 'ZLIB_CFLAGS' in os.environ.keys(): zlib_cflags = shlex.split (os.environ['ZLIB_CFLAGS']) +if 'ZLIB_LDFLAGS' in os.environ.keys(): zlib_ldflags = shlex.split (os.environ['ZLIB_LDFLAGS']) + +try: + zlib_version = compile (''' +#include +#include + +int main() { + std::cout << zlibVersion(); + return (0); +} +''', cpp_flags + zlib_cflags, ld_flags + zlib_ldflags) + report (zlib_version + '\n') +except CompileError: + error ('''compiler error! + +Use the ZLIB_CFLAGS environment variable to set the path to +the zlib include files and to set any required flags +For example: + ZLIB_CFLAGS="-I/usr/local/include" ./configure''') +except LinkError: + error ('''linker error! + +Use the ZLIB_LDFLAGS environment variable to set the path to +the zlib libraries and to set the library to use +For example: + ZLIB_LDFLAGS="-L/usr/local/lib -lz" ./configure''') +except RuntimeError: + error ('''runtime error! + +There is something wrong with your zlib implementation!''') +except: + error ('zlib implementation not found!') + +cpp_flags += zlib_cflags +ld_flags += zlib_ldflags +ld_lib_flags += zlib_ldflags + + + + + +# Eigen3 flags: + +report('checking for Eigen 3 library: ') +if 'EIGEN_CFLAGS' in os.environ.keys(): + eigen_cflags = shlex.split (os.environ['EIGEN_CFLAGS']) +else: + try: + eigen_cflags = shlex.split (execute ([ 'pkg-config', '--cflags', 'eigen3' ], RuntimeError)[1]) + except: + log('error running on pkg-config --cflags eigen3\n\n') + +try: + eigen_version = compile (''' +#include +#include + +int main (int argc, char* argv[]) { + std::cout << EIGEN_WORLD_VERSION << "." << EIGEN_MAJOR_VERSION << "." << EIGEN_MINOR_VERSION << "\\n"; + return 0; +} +''', cpp_flags + eigen_cflags, ld_flags) + + cpp_flags += eigen_cflags + report (eigen_version + '\n') +except CompileError: + error ('''compiler error! + +Use the EIGEN_CFLAGS environment variable to set the path to +the Eigen3 include files and to set any required flags +For example: + EIGEN_CFLAGS=-I/usr/include/eigen3 ./configure''') +except LinkError: + error ('''linker error! + +This shouldn't happen! Please report this to the MRtrix3 dev team. +''') +except RuntimeError: + error ('''runtime error! + +This shouldn't happen! Please report this to the MRtrix3 dev team. +''') +except: + error ('Eigen3 implementation not found!') + + +if 'FFTW3_CFLAGS' in os.environ.keys(): + fftw3_cflags = shlex.split (os.environ['FFTW3_CFLAGS']) +else: + try: + fftw3_cflags = shlex.split (execute ([ 'pkg-config', 'fftw3', '--cflags' ], RuntimeError)[1]) + fftw3_cflags = [ c for c in fftw3_cflags if c is not 0 ] + except: + log ('pkg-config not in PATH - assuming defaults for FFTW3_CFLAGS\n\n') + +if 'GSL_LDFLAGS' in os.environ.keys(): + fftw3_ldflags = shlex.split (os.environ['FFTW3_LDFLAGS']) +else: + try: + fftw3_ldflags = shlex.split (execute ([ 'pkg-config', 'fftw3', '--libs' ], RuntimeError)[1]) + fftw3_ldflags = [ c for c in fftw3_ldflags if c is not 0 ] + except: + log ('pkg-config not in PATH - assuming defaults for FFTW3_LDFLAGS\n\n') +cpp_flags += fftw3_cflags +ld_flags += fftw3_ldflags + + + +# GSL flags: + +report ('Checking for GNU Scientific Library: ') +if 'GSL_CFLAGS' in os.environ.keys(): + gsl_cflags = shlex.split (os.environ['GSL_CFLAGS']) +else: + try: + gsl_cflags = shlex.split (execute ([ 'gsl-config', '--cflags' ], RuntimeError)[1]) + gsl_cflags = [ c for c in gsl_cflags if c is not 0 ] + except: + log ('gsl-config not in PATH - assuming defaults for GSL_CFLAGS\n\n') + +if 'GSL_LDFLAGS' in os.environ.keys(): + gsl_ldflags = shlex.split (os.environ['GSL_LDFLAGS']) +else: + try: + gsl_ldflags = shlex.split (execute ([ 'gsl-config', '--libs' ], RuntimeError)[1]) + gsl_ldflags = [ c for c in gsl_ldflags if c is not 0 ] + except: + log ('gsl-config not in PATH - assuming defaults for GSL_LDFLAGS\n\n') + + +try: + gsl_version = compile (''' +#include +#include +#include + +int main() { + std::cout << gsl_version; + gsl_matrix* M = gsl_matrix_alloc (3,3); + return (M->size1 != 3); +} +''', cpp_flags + gsl_cflags, ld_flags + gsl_ldflags) + report (gsl_version + '\n') +except CompileError: + error ('''compiler error! + +Use the GSL_CFLAGS environment variable to set the path to the GSL include files' +For example:' + GSL_CFLAGS=-I/usr/local/include ./configure''') +except LinkError: + error ('''linker error!' + +Use the GSL_LDFLAGS environment variable to set the path to the GSL libraries' +and include any required libraries' +For example:' + GSL_LDFLAGS="-L/usr/local/lib -lgsl -lgslcblas" ./configure''') + +ld_lib_flags += gsl_ldflags + +report ('Checking whether GSL compiles with -DHAVE_INLINE: ') +try: + gsl_version = compile (''' +#include +int main() { + gsl_matrix* M = gsl_matrix_alloc (3,3); + gsl_matrix_set(M,0,0,3.14); + return (gsl_matrix_get(M,0,0) != 3.14); +}''', cpp_flags + gsl_cflags + [ '-DHAVE_INLINE' ], ld_flags + gsl_ldflags) + gsl_cflags += [ '-DHAVE_INLINE' ] + report ('yes\n') +except: + report ('no\n') + + + + +# check for alternate cblas libraries: + +if 'CBLAS_LDFLAGS' in os.environ.keys(): + cblas_ldflags = shlex.split (os.environ['CBLAS_LDFLAGS']) + flags = gsl_ldflags[:] + flags.remove ('-lgslcblas') + flags += cblas_ldflags; + report ('Checking whether GSL compiles with alternate C BLAS libraries ("' + ' '.join(cblas_ldflags) + '"): ') + try: + compile (''' +#include +#include +#include + +int main() { + std::cout << gsl_version; + gsl_matrix* M = gsl_matrix_alloc (3,3); + return (M->size1 != 3); +} +''', cpp_flags + gsl_cflags, ld_flags + flags) + report ('yes\n') + gsl_ldflags = flags + except: + error ('''Error compiling and/or linking with alternative C BLAS libraries provided! +Check whether the information provided by the CBLAS_LDFLAGS environment +variable is correct.''') + + + + + + + +# shared library generation: +if not noshared: + report ('Checking shared library generation: ') + + with TempFile ('.cpp') as F: + F.fid.write ('int bogus() { return (1); }') + F.fid.flush() + F.fid.close() + with DeleteAfter (F.name[:-4] + '.o') as obj: + cmd = fillin (cpp, { + 'CFLAGS': cpp_flags, + 'SRC': F.name, + 'OBJECT': obj.name }) + try: execute (cmd, CompileError) + except CompileError: + error ('''compiler not found! + + an unexpected error occurred''') + except: + raise + + with DeleteAfter (lib_prefix + 'test' + lib_suffix) as lib: + cmd = fillin (ld_lib, { + 'LDLIB_FLAGS': ld_lib_flags, + 'OBJECTS': obj.name, + 'LIB': lib.name }) + try: execute (cmd, LinkError) + except LinkError: + error ('''linker not found! + + Use the LDLIB_ARGS environment variable to set the command-line + for shared library generation''') + except: + raise + + report ('yes\n') + + + +#the following regex will be reused so keep it outside of the get_qt_version func +version_regex = re.compile(r'\d+\.\d+(\.\d+)+') #: :type version_regex: re.compile +def get_qt_version(cmd_list, raise_on_non_zero_exit_code): + out = execute (cmd_list, raise_on_non_zero_exit_code, False) + stdouterr = ' '.join(out[1:]).replace(r'\n',' ').replace(r'\r','') + version_found = version_regex.search(stdouterr) + if version_found: return version_found.group() + else: raise raise_on_non_zero_exit_code('Version not Found') + + +moc = '' +rcc = '' +qt_cflags = [] +qt_ldflags = [] + + +if not nogui: + + report ('Checking for Qt moc: ') + moc = 'moc' + if 'MOC' in os.environ.keys(): + moc = os.environ['MOC'] + try: + moc_version = get_qt_version([ moc, '-v' ], OSError) + report (moc + ' (version ' + moc_version + ')\n') + if int (moc_version.split('.')[0]) < 4: + error (''' Qt moc version is too old! + + Use the MOC environment variable to specify the Qt meta-object compiler, + or set PATH to ensure the correct version is listed first''') + except OSError: + error (''' Qt moc not found! + + Use the MOC environment variable to specify the Qt meta-object compiler, + or set PATH to include its location''') + except: + raise + + + + report ('Checking for Qt qmake: ') + qmake = 'qmake' + if 'QMAKE' in os.environ.keys(): + qmake = os.environ['QMAKE'] + try: + qmake_version = get_qt_version([ qmake, '-v' ], OSError) + report (qmake + ' (version ' + qmake_version + ')\n') + if int (qmake_version.split('.')[0]) < 4: + error (''' Qt qmake not found! + + Use the QMAKE environment variable to specify the Qt qmake executable, + or set PATH to ensure the correct version is listed first''') + except OSError: + error (''' Qt qmake not found! + + Use the QMAKE environment variable to specify the Qt qmake executable, + or set PATH to include its location''') + except: + raise + + + + report ('Checking for Qt rcc: ') + rcc = 'rcc' + + if 'RCC' in os.environ.keys(): + rcc = os.environ['RCC'] + try: + rcc_version = get_qt_version([ rcc, '-v' ], OSError) + report (rcc + ' (version ' + rcc_version + ')\n') + if int (rcc_version.split('.')[0]) < 4: + error (''' Qt rcc not found! + + Use the RCC environment variable to specify the Qt rcc executable, + or set PATH to ensure the correct version is listed first''') + except OSError: + error (''' Qt rcc not found! + + Use the RCC environment variable to specify the Qt rcc executable, + or set PATH to include its location''') + except: + raise + + + + + report ('Checking for Qt: ') + + try: + with TempDir() as qt_dir: + file = '''#include + +class Foo: public QObject { + Q_OBJECT; + public: + Foo(); + ~Foo(); + public slots: + void setValue(int value); + signals: + void valueChanged (int newValue); + private: + int value_; +}; +''' + log ('\nsource file "qt.h":\n---\n' + file + '---\n') + + f=open (os.path.join (qt_dir.name, 'qt.h'), 'w') + f.write (file) + f.close(); + + file = '''#include +#include "qt.h" + +Foo::Foo() : value_ (42) { connect (this, SIGNAL(valueChanged(int)), this, SLOT(setValue(int))); } + +Foo::~Foo() { std::cout << qVersion() << "\\n"; } + +void Foo::setValue (int value) { value_ = value; } + +int main() { Foo f; } +''' + + log ('\nsource file "qt.cpp":\n---\n' + file + '---\n') + f=open (os.path.join (qt_dir.name, 'qt.cpp'), 'w') + f.write (file) + f.close(); + + file = 'CONFIG += c++11' + if debug: file += ' debug' + file += '\nQT += core gui opengl svg\n' + file += 'HEADERS += qt.h\nSOURCES += qt.cpp\n' + + log ('\nproject file "qt.pro":\n---\n' + file + '---\n') + f=open (os.path.join (qt_dir.name, 'qt.pro'), 'w') + f.write (file) + f.close(); + + qmake_cmd = [ qmake ] + if system == 'windows': + qmake_cmd += [ '-spec', 'win32-g++' ] + + try: + (retcode, stdout, stderr) = execute (qmake_cmd, QMakeError, raise_on_non_zero_exit_code = False, cwd=qt_dir.name) + if retcode != 0: + error ('''qmake returned with error: + +''' + stderr) + except QMakeError as E: + error ('''error issuing qmake command! + + Use the QMAKE environment variable to set the correct qmake command for use with Qt''') + except: + raise + + + qt_defines = [] + qt_includes = [] + qt_cflags = [] + qt_libs = [] + qt_ldflags = [] + qt_makefile = 'Makefile' + if system == 'windows': + qt_makefile = 'Makefile.Release' + for line in open (os.path.join (qt_dir.name, qt_makefile)): + line = line.strip() + if line.startswith ('DEFINES'): + qt_defines = shlex.split (line[line.find('=')+1:].strip()) + elif line.startswith ('CXXFLAGS'): + qt_cflags = shlex.split (line[line.find('=')+1:].strip()) + elif line.startswith ('INCPATH'): + qt_includes = shlex.split (line[line.find('=')+1:].strip()) + elif line.startswith ('LIBS'): + qt_libs = shlex.split (line[line.find('=')+1:].strip()) + elif line.startswith ('LFLAGS'): + qt_ldflags = shlex.split (line[line.find('=')+1:].strip()) + + + qt = qt_cflags + qt_defines + qt_includes + qt_cflags = [] + for entry in qt: + if entry[0] != '$' and not entry == '-I.': qt_cflags += [ entry.replace('\"','').replace("'",'') ] + + qt = qt_ldflags + qt_libs + qt_ldflags = [] + for entry in qt: + if entry[0] != '$': qt_ldflags += [ entry.replace('\"','').replace("'",'') ] + + cmd = [ moc, 'qt.h', '-o', 'qt_moc.cpp' ] + log ('\nexecuting "' + ' ' .join(cmd) + '"...\n') + try: process = subprocess.Popen (cmd, cwd=qt_dir.name, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: raise QMOCError + if process.wait() != 0: raise QMOCError + + cmd = cxx + [ '-c' ] + cpp_flags + qt_cflags + [ 'qt.cpp', '-o', 'qt.o' ] + log ('\nexecuting "' + ' ' .join(cmd) + '"...\n') + try: process = subprocess.Popen (cmd, cwd=qt_dir.name, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: raise CompileError('oserror') + except: raise + retcode = process.wait() + if retcode != 0: raise CompileError('process not terminated properly (exit code = %s)'%str(retcode)) + + cmd = cxx + [ '-c' ] + cpp_flags + qt_cflags + [ 'qt_moc.cpp', '-o', 'qt_moc.o' ] + log ('\nexecuting "' + ' ' .join(cmd) + '"...\n') + try: process = subprocess.Popen (cmd , cwd=qt_dir.name, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: raise CompileError('oserror') + except: raise + if process.wait() != 0: raise CompileError('process not terminated properly') + + cmd = cxx + ld_flags + [ 'qt_moc.o', 'qt.o', '-o', 'qt' ] + qt_ldflags + log ('\nexecuting "' + ' ' .join(cmd) + '"...\n') + try: process = subprocess.Popen (cmd , cwd=qt_dir.name, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: raise LinkError('oserror') + except: raise + if process.wait() != 0: raise LinkError('process not terminated properly') + + cmd = os.path.join(qt_dir.name, 'qt') + log ('\nexecuting "' + cmd + '"...\n') + process = subprocess.Popen (cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if process.wait() != 0: raise LinkError('process not terminated properly') + report (process.stdout.read().decode(errors='ignore').strip() + '\n') + + + except QMakeError: + error ('''error invoking Qt qmake! + + Use the QMAKE environment variable to set the correct qmake command for use with Qt''') + except QMOCError: + error ('''error invoking Qt moc! + +use the MOC environment variable to specify the Qt moc command''') + except LinkError: + error ('''error linking Qt application! + +Are all necessary (static or shared) Qt libraries installed?''') + except QMOCError: + error ('''error invoking Qt moc! + +use the MOC environment variable to specify the Qt moc command''') + except CompileError as e: + error ('error compiling Qt application! ' + str(e)) + except OSError as e: + error ('Unexpected error! Unable to configure Qt environment: ' + str(e)) + except: + raise + + + + + + if system == "darwin": + if '-Wall' in qt_cflags: qt_cflags.remove ('-Wall') + if '-W' in qt_cflags: qt_cflags.remove ('-W') + + +# output R module: +if R_module: + + report ('Checking for R library: ') + R_cflags = [ '-I/usr/include/R' ] + if 'R_CFLAGS' in os.environ.keys(): + R_cflags = shlex.split (os.environ['R_CFLAGS']) + else: + try: + R_cflags = shlex.split (execute ([ 'pkg-config', '--cflags', 'libR' ], RuntimeError)[1]) + except: + log ('error running pkg-config --libs libR - assuming defaults for R_CFLAGS\n\n') + + R_ldflags = [ '-L/usr/lib/R/lib', '-lR' ] + if 'R_LDFLAGS' in os.environ.keys(): + R_ldflags = shlex.split (os.environ['R_LDFLAGS']) + else: + try: + R_ldflags = shlex.split (execute ([ 'pkg-config', '--libs', 'libR' ], RuntimeError)[1]) + except: + log ('error running pkg-config --libs libR - assuming defaults for R_LDFLAGS\n\n') + + + try: + R_version = compile (''' + #include + #include + #include + + int main() { + std::cout << R_MAJOR << "." << R_MINOR << " (r" << R_SVN_REVISION << ")\\n"; + return 0; + } + ''', cpp_flags + R_cflags, ld_flags + R_ldflags) + report (R_version + '\n') + except CompileError: + error ('''compiler error! + + Use the R_CFLAGS environment variable to set the path to the R include files' + For example:' + R_CFLAGS=-I/usr/local/include/R ./configure''') + except LinkError: + error ('''linker error!' + + Use the R_LDFLAGS environment variable to set the path to the R library' + and include any required libraries' + For example:' + R_LDFLAGS="-L/usr/local/R/lib -lR" ./configure''') + except RuntimeError: + error ('''error running command - check configure.log for details''') + except: + raise + + cpp_flags += R_cflags + [ '-DMRTRIX_AS_R_LIBRARY' ] + ld_lib_flags += R_ldflags + + ld_flags = ld_lib_flags + exe_suffix = lib_suffix + + + + +# add debugging or profiling flags if requested: + +cpp_flags += [ '-Wall' ] + +if profile: + cpp_flags += [ '-g', '-pg' ] + ld_flags += [ '-g', '-pg' ] + ld_lib_flags += [ '-g', '-pg' ] +elif debug: + cpp_flags += [ '-O0', '-g' ] + ld_flags += [ '-g' ] + ld_lib_flags += [ '-g' ] +else: + cpp_flags += [ '-O2' ] + +if asserts: + cpp_flags += [ '-D_GLIBCXX_DEBUG=1', '-D_GLIBCXX_DEBUG_PEDANTIC=1' ] +else: + cpp_flags += [ '-DNDEBUG' ] + + + +# +# set macro for non-orthonormal SH basis if requested: +if sh_basis_def is not None: + cpp_flags += [ sh_basis_def ] + + +# write out configuration: +cache_filename = os.path.join (os.path.dirname(sys.argv[0]), profile_name, 'config') + +sys.stdout.write ('\nwriting configuration to file \'' + cache_filename + '\': ') + +cache = open (cache_filename, 'w') + +cache.write ("""#!/usr/bin/python +# +# autogenerated by MRtrix configure script +# +# configure output: +""") +for line in config_report.splitlines(): + cache.write ('# ' + line + '\n') +cache.write ('\n\n') + +path = os.environ['PATH'] +if path.endswith ('\\'): + path = path[:-1] +cache.write ("PATH = r'" + path + "'\n") + +commit ('obj_suffix', obj_suffix) +commit ('exe_suffix', exe_suffix) +commit ('lib_prefix', lib_prefix) +commit ('lib_suffix', lib_suffix) +commit ('cpp', cpp); +commit ('cpp_flags', cpp_flags); +commit ('ld', ld); +commit ('ld_flags', ld_flags); +commit ('runpath', runpath); +cache.write ('ld_enabled = ') +if noshared: + cache.write ('False\n') +else: + cache.write ('True\n') + commit ('ld_lib', ld_lib); + commit ('ld_lib_flags', ld_lib_flags); +commit ('gsl_cflags', gsl_cflags) +commit ('gsl_ldflags', gsl_ldflags) + +commit ('moc', moc) +commit ('rcc', rcc) +commit ('qt_cflags', qt_cflags) +commit ('qt_ldflags', qt_ldflags) +cache.write ('nogui = ') +if nogui: + cache.write ('True\n') +else: + cache.write ('False\n') + +cache.close() +sys.stdout.write ('ok\n\n') + From 04e381c241c490b0bbf6a94eec22edda33d3ac06 Mon Sep 17 00:00:00 2001 From: rtabbara Date: Tue, 28 Jun 2016 15:17:55 +1000 Subject: [PATCH 003/139] Proposed solution for Viewing each volume of a 4D image in Lightbox mode #451 --- src/gui/mrview/gui_image.cpp | 35 ++++++++++++++++ src/gui/mrview/gui_image.h | 4 ++ src/gui/mrview/mode/lightbox.cpp | 69 ++++++++++++++++++++++++++++---- src/gui/mrview/mode/lightbox.h | 16 ++++++-- src/gui/mrview/tool/view.cpp | 59 ++++++++++++++++++++------- src/gui/mrview/tool/view.h | 9 +++-- src/gui/opengl/gl.h | 5 ++- 7 files changed, 168 insertions(+), 29 deletions(-) diff --git a/src/gui/mrview/gui_image.cpp b/src/gui/mrview/gui_image.cpp index 5b63138546..2894875f44 100644 --- a/src/gui/mrview/gui_image.cpp +++ b/src/gui/mrview/gui_image.cpp @@ -301,6 +301,8 @@ namespace MR void Image::update_texture3D () { + lookup_texture_4D_cache(); + // Binding also guarantees texture interpolation is updated bind(); @@ -411,8 +413,41 @@ namespace MR copy_texture_3D_complex(); min_max_set (); + update_texture_4D_cache (); } + + inline void Image::lookup_texture_4D_cache () + { + if (!volume_unchanged() && !texture_mode_changed) { + size_t vol_idx = image.index(3); + auto cached_tex = tex_4d_cache.find(vol_idx); + if (cached_tex != tex_4d_cache.end()) { + _texture = cached_tex->second; + tex_positions[3] = vol_idx; + } else { + auto tex = GL::Texture(); + _texture = tex; + tex_positions[3] = -1; + } + + bind(); + + // Reset cache in case we've stored too many 3d textures + if (gl::GetError () == gl::OUT_OF_MEMORY) { + tex_4d_cache.clear(); + _texture = GL::Texture(); + } + } + } + + inline void Image::update_texture_4D_cache () + { + if (image.ndim() == 4) + tex_4d_cache[image.index(3)] = _texture; + } + + // required to shut up clang's compiler warnings about std::abs() when // instantiating Image::copy_texture_3D() with unsigned types: template diff --git a/src/gui/mrview/gui_image.h b/src/gui/mrview/gui_image.h index 9084d538d5..4453345fcb 100644 --- a/src/gui/mrview/gui_image.h +++ b/src/gui/mrview/gui_image.h @@ -22,6 +22,7 @@ #include "gui/mrview/volume.h" #include "interp/linear.h" #include "interp/nearest.h" +#include namespace MR @@ -88,6 +89,7 @@ namespace MR friend class Tool::ODF; std::array slice_min, slice_max; + std::unordered_map tex_4d_cache; private: bool volume_unchanged (); @@ -96,6 +98,8 @@ namespace MR template void copy_texture_3D (); void copy_texture_3D_complex (); + void lookup_texture_4D_cache (); + void update_texture_4D_cache (); std::vector _comments; diff --git a/src/gui/mrview/mode/lightbox.cpp b/src/gui/mrview/mode/lightbox.cpp index 7064df88a3..f56f8e2671 100644 --- a/src/gui/mrview/mode/lightbox.cpp +++ b/src/gui/mrview/mode/lightbox.cpp @@ -25,8 +25,10 @@ namespace MR { bool LightBox::show_grid_lines(true); + bool LightBox::show_volumes(false); size_t LightBox::n_rows(3); size_t LightBox::n_cols(5); + size_t LightBox::volume_increment(1); float LightBox::slice_focus_increment(1.f); float LightBox::slice_focus_inc_adjust_rate(0.2f); std::string LightBox::prev_image_name; @@ -40,8 +42,10 @@ namespace MR if(!img || prev_image_name != img->header().name()) image_changed_event(); - else + else { + set_volume_increment(1); set_slice_increment(slice_focus_increment); + } } @@ -59,6 +63,13 @@ namespace MR updateGL(); } + void LightBox::set_volume_increment(size_t vol_inc) + { + volume_increment = vol_inc; + update_volume_indices(); + updateGL(); + } + void LightBox::set_slice_increment(float inc) { slice_focus_increment = inc; @@ -72,25 +83,43 @@ namespace MR updateGL(); } + void LightBox::set_show_volumes(bool show_vol) + { + show_volumes = show_vol; + updateGL(); + } + + inline bool LightBox::render_volumes() + { + return show_volumes && image () && image()->image.ndim() == 4; + } + inline void LightBox::update_layout() { // Can't use std::vector resize() because Projection needs to be assignable slices_proj_focusdelta = std::vector( n_cols * n_rows, proj_focusdelta(projection, 0.f)); + set_current_slice_index((n_rows * n_cols) / 2); update_slices_focusdelta(); + update_volume_indices(); + frame_VB.clear(); frame_VAO.clear(); } void LightBox::set_current_slice_index(size_t slice_index) { - size_t prev_index = current_slice_index; + int prev_index = current_slice_index; current_slice_index = slice_index; - if(prev_index != current_slice_index) { + if (render_volumes()) { + window().set_image_volume(3, volume_indices[current_slice_index]); + } + + else if (prev_index != (int)current_slice_index) { const Projection& slice_proj = slices_proj_focusdelta[current_slice_index].first; float focus_delta = slices_proj_focusdelta[current_slice_index].second; @@ -109,6 +138,21 @@ namespace MR } } + void LightBox::update_volume_indices() + { + bool is_4d = image () && image()->image.ndim() == 4; + volume_indices.resize (n_rows * n_cols, 0); + + if (!is_4d) + return; + + int n_vols = image()->image.size(3); + int initial_vol = image()->image.index(3); + + for(int i = 0, N = volume_indices.size(); i < N; ++i) + volume_indices[i] = std::min(std::max(initial_vol + (int)volume_increment * (i - (int)current_slice_index), 0), n_vols - 1); + } + void LightBox::draw_plane_primitive (int axis, Displayable::Shader& shader_program, Projection& with_projection) { ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; @@ -133,11 +177,13 @@ namespace MR const Eigen::Vector3f orig_focus = window().focus(); - if(layout_is_dirty) { + if (layout_is_dirty) { update_layout(); layout_is_dirty = false; } + bool rend_vols = render_volumes(); + size_t slice_idx = 0; for(size_t row = 0; row < n_rows; ++row) { for(size_t col = 0; col < n_cols; ++col, ++slice_idx) { @@ -150,9 +196,14 @@ namespace MR // because move_in_out_displacement is reliant on MVP setup_projection (plane(), slice_proj); - float focus_delta = slices_proj_focusdelta[slice_idx].second; - Eigen::Vector3f slice_focus = move_in_out_displacement(focus_delta, slice_proj); - set_focus(orig_focus + slice_focus); + if (rend_vols) + image()->image.index(3) = volume_indices[slice_idx]; + + else { + float focus_delta = slices_proj_focusdelta[slice_idx].second; + Eigen::Vector3f slice_focus = move_in_out_displacement(focus_delta, slice_proj); + set_focus(orig_focus + slice_focus); + } draw_plane_primitive(plane(), slice_shader, slice_proj); @@ -167,6 +218,8 @@ namespace MR } // Restore view state + if(rend_vols) + image()->image.index(3) = volume_indices[current_slice_index]; set_focus(orig_focus); projection.set_viewport(window(), x, y, w, h); @@ -279,6 +332,8 @@ namespace MR { Base::image_changed_event(); + update_volume_indices(); + if(image()) { const auto& header = image()->header(); if (prev_image_name.empty()) { diff --git a/src/gui/mrview/mode/lightbox.h b/src/gui/mrview/mode/lightbox.h index a09076b474..ce7f6fd709 100644 --- a/src/gui/mrview/mode/lightbox.h +++ b/src/gui/mrview/mode/lightbox.h @@ -45,20 +45,27 @@ namespace MR static size_t get_rows() { return n_rows; } static size_t get_cols() { return n_cols; } + static size_t get_volume_increment() { return volume_increment; } static float get_slice_increment() { return slice_focus_increment; } static float get_slice_inc_adjust_rate() { return slice_focus_inc_adjust_rate; } static bool get_show_grid() { return show_grid_lines; } + static bool get_show_volumes() { return show_volumes; } void set_rows(size_t rows); void set_cols(size_t cols); + void set_volume_increment(size_t vol_inc); void set_slice_increment(float inc); void set_show_grid(bool show_grid); + void set_show_volumes(bool show_vol); public slots: void nrows_slot(int value) { set_rows(static_cast(value)); } void ncolumns_slot(int value) { set_cols(static_cast(value));} - void slice_inc_slot(float value) { set_slice_increment(value);} + void slice_inc_slot(float value) { set_slice_increment(value); } + void volume_inc_slot(int value) { set_volume_increment(value); } void show_grid_slot (bool value) { set_show_grid(value); } + void show_volumes_slot (bool value) { set_show_volumes(value); } + void image_volume_changed_slot() { update_volume_indices(); } protected: void draw_plane_primitive(int axis, Displayable::Shader& shader_program, @@ -71,19 +78,22 @@ namespace MR } void update_layout(); + void update_volume_indices(); void update_slices_focusdelta(); void set_current_slice_index(size_t slice_index); void draw_grid(); + bool render_volumes(); // Want layout state to persist even after instance is destroyed - static bool show_grid_lines; + static bool show_grid_lines, show_volumes; static std::string prev_image_name; - static size_t n_rows, n_cols; + static size_t n_rows, n_cols, volume_increment; static float slice_focus_increment; static float slice_focus_inc_adjust_rate; bool layout_is_dirty; size_t current_slice_index; + std::vector volume_indices; std::vector slices_proj_focusdelta; GL::VertexBuffer frame_VB; diff --git a/src/gui/mrview/tool/view.cpp b/src/gui/mrview/tool/view.cpp index 87da34907f..d4ae118d47 100644 --- a/src/gui/mrview/tool/view.cpp +++ b/src/gui/mrview/tool/view.cpp @@ -496,6 +496,8 @@ namespace MR setEnabled (image); + reset_light_box_gui_controls(); + if (!image) return; @@ -933,6 +935,10 @@ namespace MR reset_light_box_gui_controls(); } + void View::light_box_toggle_volumes_slot(bool) + { + reset_light_box_gui_controls(); + } @@ -941,6 +947,7 @@ namespace MR using LightBoxEditButton = MRView::Mode::LightBoxViewControls::LightBoxEditButton; light_box_slice_inc = new AdjustButton(this); + light_box_volume_inc = new LightBoxEditButton(this); light_box_rows = new LightBoxEditButton(this); light_box_cols = new LightBoxEditButton(this); @@ -951,31 +958,51 @@ namespace MR GridLayout* grid_layout = new GridLayout; lightbox_box->setLayout(grid_layout); - grid_layout->addWidget(new QLabel (tr("Slice increment (mm):")), 0, 1); - grid_layout->addWidget(light_box_slice_inc, 0, 2); + light_box_slice_inc_label = new QLabel (tr("Slice increment (mm):")); + grid_layout->addWidget(light_box_slice_inc_label, 1, 0); + grid_layout->addWidget(light_box_slice_inc, 1, 2); + light_box_volume_inc_label = new QLabel (tr("Volume increment:")); + grid_layout->addWidget(light_box_volume_inc_label, 1, 0); + grid_layout->addWidget(light_box_volume_inc, 1, 2); - grid_layout->addWidget(new QLabel (tr("Rows:")), 1, 1); - grid_layout->addWidget(light_box_rows, 1, 2); + grid_layout->addWidget(new QLabel (tr("Rows:")), 2, 0); + grid_layout->addWidget(light_box_rows, 2, 2); - grid_layout->addWidget (new QLabel (tr("Columns:")), 2, 1); - grid_layout->addWidget(light_box_cols, 2, 2); + grid_layout->addWidget (new QLabel (tr("Columns:")), 3, 0); + grid_layout->addWidget(light_box_cols, 3, 2); + + light_box_show_4d = new QCheckBox(tr("Show 4d volumes"), this); + grid_layout->addWidget(light_box_show_4d, 4, 0, 1, 2); light_box_show_grid = new QCheckBox(tr("Show grid"), this); - grid_layout->addWidget(light_box_show_grid, 3, 0, 1, 2); + grid_layout->addWidget(light_box_show_grid, 5, 0, 1, 2); } - - void View::reset_light_box_gui_controls() { - light_box_rows->setValue(static_cast(Mode::LightBox::get_rows())); - light_box_cols->setValue(static_cast(Mode::LightBox::get_cols())); - light_box_slice_inc->setValue(Mode::LightBox::get_slice_increment()); - light_box_slice_inc->setRate(Mode::LightBox::get_slice_inc_adjust_rate()); - light_box_show_grid->setChecked(Mode::LightBox::get_show_grid()); + if (!lightbox_box) + return; + + bool img_4d = window ().image() && window ().image()->image.ndim() == 4; + bool show_volumes = Mode::LightBox::get_show_volumes (); + bool can_show_vol = img_4d && show_volumes; + + light_box_rows->setValue (static_cast(Mode::LightBox::get_rows ())); + light_box_cols->setValue (static_cast(Mode::LightBox::get_cols ())); + light_box_slice_inc->setValue (Mode::LightBox::get_slice_increment ()); + light_box_slice_inc->setRate (Mode::LightBox::get_slice_inc_adjust_rate ()); + light_box_volume_inc->setValue (Mode::LightBox::get_volume_increment ()); + light_box_show_grid->setChecked (Mode::LightBox::get_show_grid ()); + + light_box_show_4d->setEnabled (img_4d); + light_box_show_4d->setChecked (can_show_vol); + light_box_slice_inc_label->setVisible (!can_show_vol); + light_box_slice_inc->setVisible (!can_show_vol); + light_box_volume_inc_label->setVisible (can_show_vol); + light_box_volume_inc->setVisible (can_show_vol); } @@ -991,7 +1018,11 @@ namespace MR connect(light_box_rows, SIGNAL (valueChanged(int)), &mode, SLOT (nrows_slot(int))); connect(light_box_cols, SIGNAL (valueChanged(int)), &mode, SLOT (ncolumns_slot(int))); connect(light_box_slice_inc, SIGNAL (valueChanged(float)), &mode, SLOT (slice_inc_slot(float))); + connect(light_box_volume_inc, SIGNAL (valueChanged(int)), &mode, SLOT (volume_inc_slot(int))); connect(light_box_show_grid, SIGNAL (toggled(bool)), &mode, SLOT (show_grid_slot(bool))); + connect(light_box_show_4d, SIGNAL (toggled(bool)), &mode, SLOT (show_volumes_slot(bool))); + connect(light_box_show_4d, SIGNAL (toggled(bool)), this, SLOT (light_box_toggle_volumes_slot(bool))); + connect(&window(), SIGNAL (volumeChanged(size_t)), &mode, SLOT (image_volume_changed_slot())); reset_light_box_gui_controls(); } diff --git a/src/gui/mrview/tool/view.h b/src/gui/mrview/tool/view.h index 0f9453bdf8..4d64a42baa 100644 --- a/src/gui/mrview/tool/view.h +++ b/src/gui/mrview/tool/view.h @@ -100,6 +100,7 @@ namespace MR void clip_planes_clear_slot (); void light_box_slice_inc_reset_slot (); + void light_box_toggle_volumes_slot (bool); private: QPushButton *hide_button; @@ -117,9 +118,11 @@ namespace MR QAction *clip_planes_new_axial_action, *clip_planes_new_sagittal_action, *clip_planes_new_coronal_action; QAction *clip_planes_reset_axial_action, *clip_planes_reset_sagittal_action, *clip_planes_reset_coronal_action; QAction *clip_planes_invert_action, *clip_planes_remove_action, *clip_planes_clear_action; - AdjustButton* light_box_slice_inc; - SpinBox *light_box_rows, *light_box_cols; - QCheckBox *light_box_show_grid; + + QLabel *light_box_slice_inc_label, *light_box_volume_inc_label; + AdjustButton *light_box_slice_inc; + SpinBox *light_box_rows, *light_box_cols, *light_box_volume_inc; + QCheckBox *light_box_show_grid, *light_box_show_4d; class ClipPlaneModel; ClipPlaneModel* clip_planes_model; diff --git a/src/gui/opengl/gl.h b/src/gui/opengl/gl.h index 10e7d8a69f..f84e0efa2b 100644 --- a/src/gui/opengl/gl.h +++ b/src/gui/opengl/gl.h @@ -113,8 +113,9 @@ namespace MR Texture () : id (0) { } ~Texture () { clear(); } Texture (const Texture&) : id (0) { } - Texture (Texture&& t) : id (t.id) { t.id = 0; } - Texture& operator= (Texture&& t) { clear(); id = t.id; t.id = 0; return *this; } + Texture (Texture&& t) : id (t.id), tex_type (t.tex_type) { t.id = 0; } + Texture& operator= (Texture&& t) { id = t.id; tex_type = t.tex_type; t.id = 0; return *this; } + Texture& operator= (const Texture& t) { id = t.id; tex_type = t.tex_type; return *this; } operator GLuint () const { return id; } void gen (GLenum target, GLint interp_type = gl::LINEAR) { if (!id) { From ca8df740ce526eb1f0646d48762bf1f8dcedc8b4 Mon Sep 17 00:00:00 2001 From: rtabbara Date: Tue, 28 Jun 2016 15:43:34 +1000 Subject: [PATCH 004/139] Add GL::Texture cache_copy --- src/gui/mrview/gui_image.cpp | 9 ++++----- src/gui/opengl/gl.h | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/gui/mrview/gui_image.cpp b/src/gui/mrview/gui_image.cpp index 2894875f44..bb899f3776 100644 --- a/src/gui/mrview/gui_image.cpp +++ b/src/gui/mrview/gui_image.cpp @@ -423,11 +423,10 @@ namespace MR size_t vol_idx = image.index(3); auto cached_tex = tex_4d_cache.find(vol_idx); if (cached_tex != tex_4d_cache.end()) { - _texture = cached_tex->second; + _texture.cache_copy (cached_tex->second); tex_positions[3] = vol_idx; } else { - auto tex = GL::Texture(); - _texture = tex; + _texture.cache_copy(GL::Texture()); tex_positions[3] = -1; } @@ -436,7 +435,7 @@ namespace MR // Reset cache in case we've stored too many 3d textures if (gl::GetError () == gl::OUT_OF_MEMORY) { tex_4d_cache.clear(); - _texture = GL::Texture(); + _texture.cache_copy(GL::Texture()); } } } @@ -444,7 +443,7 @@ namespace MR inline void Image::update_texture_4D_cache () { if (image.ndim() == 4) - tex_4d_cache[image.index(3)] = _texture; + tex_4d_cache[image.index(3)].cache_copy(_texture); } diff --git a/src/gui/opengl/gl.h b/src/gui/opengl/gl.h index f84e0efa2b..369d639fde 100644 --- a/src/gui/opengl/gl.h +++ b/src/gui/opengl/gl.h @@ -114,8 +114,8 @@ namespace MR ~Texture () { clear(); } Texture (const Texture&) : id (0) { } Texture (Texture&& t) : id (t.id), tex_type (t.tex_type) { t.id = 0; } - Texture& operator= (Texture&& t) { id = t.id; tex_type = t.tex_type; t.id = 0; return *this; } - Texture& operator= (const Texture& t) { id = t.id; tex_type = t.tex_type; return *this; } + Texture& operator= (Texture&& t) { clear(); id = t.id; tex_type = t.tex_type; t.id = 0; return *this; } + void cache_copy(const Texture& t) { id = t.id; tex_type = t.tex_type; } operator GLuint () const { return id; } void gen (GLenum target, GLint interp_type = gl::LINEAR) { if (!id) { From 52f273ba1a648768c618741a95c22bc49a5d13fe Mon Sep 17 00:00:00 2001 From: rtabbara Date: Tue, 28 Jun 2016 16:33:32 +1000 Subject: [PATCH 005/139] Lightbox 4d: Cleaning up --- src/gui/mrview/gui_image.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/gui/mrview/gui_image.cpp b/src/gui/mrview/gui_image.cpp index bb899f3776..7a640b17a7 100644 --- a/src/gui/mrview/gui_image.cpp +++ b/src/gui/mrview/gui_image.cpp @@ -431,12 +431,6 @@ namespace MR } bind(); - - // Reset cache in case we've stored too many 3d textures - if (gl::GetError () == gl::OUT_OF_MEMORY) { - tex_4d_cache.clear(); - _texture.cache_copy(GL::Texture()); - } } } From 6fdb30aabc5714c6e36636c6db8feb4beaa6a539 Mon Sep 17 00:00:00 2001 From: rei19q Date: Mon, 10 Apr 2017 13:43:05 +1000 Subject: [PATCH 006/139] Added command options for mrview: tractography.load (loads a track file) tractography.slab (slab thickness) tractography.tsf (uses a tsf file for track node colours) WARNING: tsf optional arguments do not appear to actually be optional due to pre-existing code enforcing 'optional' args (bug?). --- .../tool/tractography/track_scalar_file.cpp | 64 +++++++++---- .../tool/tractography/track_scalar_file.h | 10 ++- .../mrview/tool/tractography/tractogram.cpp | 2 +- src/gui/mrview/tool/tractography/tractogram.h | 6 +- .../tool/tractography/tractogram_enums.h | 45 ++++++++++ .../mrview/tool/tractography/tractography.cpp | 90 +++++++++++++++++-- .../mrview/tool/tractography/tractography.h | 3 +- 7 files changed, 191 insertions(+), 29 deletions(-) create mode 100644 src/gui/mrview/tool/tractography/tractogram_enums.h diff --git a/src/gui/mrview/tool/tractography/track_scalar_file.cpp b/src/gui/mrview/tool/tractography/track_scalar_file.cpp index 1c28b64640..113d9b2abb 100644 --- a/src/gui/mrview/tool/tractography/track_scalar_file.cpp +++ b/src/gui/mrview/tool/tractography/track_scalar_file.cpp @@ -12,11 +12,11 @@ * For more details, see www.mrtrix.org * */ - + #include "gui/mrview/tool/tractography/track_scalar_file.h" #include "gui/dialog/file.h" #include "gui/mrview/colourmap.h" -#include "gui/mrview/tool/tractography/tractogram.h" +#include "gui/mrview/tool/tractography/tractogram.h" namespace MR @@ -247,21 +247,24 @@ namespace MR bool TrackScalarFileOptions::open_intensity_track_scalar_file_slot () { std::string scalar_file = Dialog::File::get_file (this, "Select scalar text file or Track Scalar file (.tsf) to open", ""); - if (!scalar_file.empty()) { - try { - tractogram->load_intensity_track_scalars (scalar_file); - tractogram->set_color_type (TrackColourType::ScalarFile); - } - catch (Exception& E) { - E.display(); - scalar_file.clear(); - } - } - update_UI(); - window().updateGL(); - return scalar_file.size(); + return open_intensity_track_scalar_file_slot(scalar_file); } - + bool TrackScalarFileOptions::open_intensity_track_scalar_file_slot(std::string scalar_file) + { + if (!scalar_file.empty()) { + try { + tractogram->load_intensity_track_scalars(scalar_file); + tractogram->set_color_type(TrackColourType::ScalarFile); + } + catch (Exception& E) { + E.display(); + scalar_file.clear(); + } + } + update_UI(); + window().updateGL(); + return scalar_file.size(); + } void TrackScalarFileOptions::show_colour_bar_slot () { @@ -284,6 +287,35 @@ namespace MR } } + + + void TrackScalarFileOptions::set_threshold(GUI::MRView::Tool::TrackThresholdType dataSource, default_type min, default_type max)//TrackThresholdType dataSource + { + if (tractogram) { + //Source + tractogram->set_threshold_type(dataSource); + //Range + if (dataSource != TrackThresholdType::None) + { + tractogram->lessthan = min; + tractogram->greaterthan = max; + threshold_lower_box->setChecked(true); + threshold_upper_box->setChecked(true); + } + + update_UI(); + window().updateGL(); + } + } + + void TrackScalarFileOptions::set_scaling(default_type min, default_type max) + { + if (tractogram) { + tractogram->set_windowing(min,max); + update_UI(); + window().updateGL(); + } + } void TrackScalarFileOptions::on_set_scaling_slot () { diff --git a/src/gui/mrview/tool/tractography/track_scalar_file.h b/src/gui/mrview/tool/tractography/track_scalar_file.h index 10361ec377..17c2cc35e7 100644 --- a/src/gui/mrview/tool/tractography/track_scalar_file.h +++ b/src/gui/mrview/tool/tractography/track_scalar_file.h @@ -8,7 +8,7 @@ * 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 * */ @@ -19,6 +19,8 @@ #include "gui/mrview/adjust_button.h" #include "gui/mrview/displayable.h" #include "gui/mrview/tool/base.h" +#include "gui/mrview/tool/tractography/tractogram_enums.h" + namespace MR @@ -30,7 +32,6 @@ namespace MR namespace Tool { - class Tractogram; class TrackScalarFileOptions : public QGroupBox, public DisplayableVisitor @@ -46,9 +47,14 @@ namespace MR void render_tractogram_colourbar (const Tool::Tractogram&) override; void update_UI(); + void set_scaling(default_type min, default_type max); + void set_threshold(GUI::MRView::Tool::TrackThresholdType dataSource, default_type min, default_type max); + public slots: bool open_intensity_track_scalar_file_slot (); + bool open_intensity_track_scalar_file_slot(std::string); + private slots: void show_colour_bar_slot(); diff --git a/src/gui/mrview/tool/tractography/tractogram.cpp b/src/gui/mrview/tool/tractography/tractogram.cpp index 52951b7049..a1e8c89f29 100644 --- a/src/gui/mrview/tool/tractography/tractogram.cpp +++ b/src/gui/mrview/tool/tractography/tractogram.cpp @@ -11,7 +11,7 @@ * * For more details, see www.mrtrix.org * - */ + */ #include "progressbar.h" #include "gui/mrview/tool/tractography/tractogram.h" diff --git a/src/gui/mrview/tool/tractography/tractogram.h b/src/gui/mrview/tool/tractography/tractogram.h index d3b0832dfe..352582f8c3 100644 --- a/src/gui/mrview/tool/tractography/tractogram.h +++ b/src/gui/mrview/tool/tractography/tractogram.h @@ -13,9 +13,10 @@ * */ -#ifndef __gui_mrview_tool_tractogram_h__ +#ifndef __gui_mrview_tool_tractogram_h__ #define __gui_mrview_tool_tractogram_h__ +//#include "gui/mrview/tool/tractography/tractogram_enums.h" #include "gui/mrview/displayable.h" #include "dwi/tractography/properties.h" #include "gui/mrview/tool/tractography/tractography.h" @@ -36,9 +37,6 @@ namespace MR namespace Tool { - enum class TrackColourType { Direction, Ends, Manual, ScalarFile }; - enum class TrackThresholdType { None, UseColourFile, SeparateFile }; - class Tractogram : public Displayable { Q_OBJECT diff --git a/src/gui/mrview/tool/tractography/tractogram_enums.h b/src/gui/mrview/tool/tractography/tractogram_enums.h new file mode 100644 index 0000000000..6b47528d3a --- /dev/null +++ b/src/gui/mrview/tool/tractography/tractogram_enums.h @@ -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 + * + */ + +#ifndef __gui_mrview_tool_tractogram_enums_h__ +#define __gui_mrview_tool_tractogram_enums_h__ + +//#include "gui/mrview/displayable.h" +//#include "dwi/tractography/properties.h" +//#include "gui/mrview/tool/tractography/tractography.h" +//#include "gui/mrview/colourmap.h" + + +namespace MR +{ + + namespace GUI + { + + namespace MRView + { + namespace Tool + { + + enum class TrackColourType { Direction, Ends, Manual, ScalarFile }; + enum class TrackThresholdType { None, UseColourFile, SeparateFile }; + } + } + } +} + + +#endif + diff --git a/src/gui/mrview/tool/tractography/tractography.cpp b/src/gui/mrview/tool/tractography/tractography.cpp index fff60ee761..1213e9d2f5 100644 --- a/src/gui/mrview/tool/tractography/tractography.cpp +++ b/src/gui/mrview/tool/tractography/tractography.cpp @@ -183,7 +183,7 @@ namespace MR general_opt_grid->addWidget (new QLabel ("line thickness"), 1, 0); general_opt_grid->addWidget (thickness_slider, 1, 1); - QGroupBox* slab_group_box = new QGroupBox (tr("crop to slab")); + slab_group_box = new QGroupBox (tr("crop to slab")); slab_group_box->setCheckable (true); slab_group_box->setChecked (true); general_opt_grid->addWidget (slab_group_box, 4, 0, 1, 2); @@ -532,6 +532,7 @@ namespace MR QMessageBox::Ok); return; } + Tractogram* tractogram = tractogram_list_model->get_tractogram (indices[0]); scalar_file_options->set_tractogram (tractogram); if (tractogram->intensity_scalar_filename.empty()) { @@ -670,22 +671,85 @@ namespace MR + Option ("tractography.opacity", "Opacity of tractography display, [0.0, 1.0], default is 1.0.").allow_multiple() + Argument("value").type_float ( 0.0, 1.0 ) + + + Option("tractography.slab", "Slab thickness of tractography display, in mm. -1 to turn off crop to slab.").allow_multiple() + + Argument("value").type_float(-1, 1e6) + + + Option ("tractography.tsf", "Load the track scalar file. Filename RangeMin,RangeMax,ThresholdMin,ThesholdMax (Range and threshold optional).").allow_multiple() + + Argument("tsf").type_file_in() + + Argument("range").type_sequence_float().optional() ; } bool Tractography::process_commandline_option (const MR::App::ParsedOption& opt) { - if (opt.opt->is ("tractography.load")) { + + if (opt.opt->is ("tractography.load")) + { std::vector list (1, std::string(opt[0])); - try { - tractogram_list_model->add_items (list, *this); - window().updateGL(); + try + { + tractogram_list_model->add_items (list, *this); + window().updateGL(); + return true; } catch (Exception& E) { E.display(); } return true; } + + if (opt.opt->is("tractography.tsf")) + { + try + { + int count = tractogram_list_model->rowCount(); + if (count == 0) + { + QMessageBox::warning(QApplication::activeWindow(), + tr("Tractogram colour error"), + tr("TSF specified but no tractography loaded. Ensure TSF argument follows the tractography.load argument."), + QMessageBox::Ok, + QMessageBox::Ok); + } + else + { + //Select the last loaded tck file in the gui + QModelIndex index = tractogram_list_view->model()->index(count-1, 0); + tractogram_list_view->setCurrentIndex(index); + + //set its tsf filename and load the tsf file + Tractogram* tractogram = dynamic_cast(tractogram_list_model->items[index.row()].get()); + scalar_file_options->set_tractogram(tractogram); + scalar_file_options->open_intensity_track_scalar_file_slot(std::string(opt[0])); + + //Set the GUI to use the file for visualisation + colour_combobox->setCurrentIndex(4); // Set combobox to "File" + + //Set the visualisation range/threshold if supplied + if (opt.opt->size() > 1) + { + std::vector range = opt[1].as_sequence_float(); + + if (range.size() > 1) + { + //Range supplied + scalar_file_options->set_scaling(range[0], range[1]); + + if (range.size() > 3) + { + //Thresholds supplied + scalar_file_options->set_threshold(TrackThresholdType::UseColourFile, range[2], range[3]); + } + } + } + } + } + catch(Exception& E) { E.display(); } + + return true; + } + if (opt.opt->is ("tractography.thickness")) { // Thickness runs from -1000 to 1000, float thickness = float(opt[0]) * 1000.0f; @@ -706,6 +770,22 @@ namespace MR return true; } + if (opt.opt->is("tractography.slab")) { + float thickness = opt[0]; + try { + bool crop = thickness > 0; + slab_group_box->setChecked(crop); + on_crop_to_slab_slot(crop);//Needs to be manually bumped + if(crop) + { + slab_entry->setValue(thickness); + on_slab_thickness_slot();//Needs to be manually bumped + } + } + catch (Exception& E) { E.display(); } + return true; + } + return false; } diff --git a/src/gui/mrview/tool/tractography/tractography.h b/src/gui/mrview/tool/tractography/tractography.h index 99154ed2ab..e58acd95f6 100644 --- a/src/gui/mrview/tool/tractography/tractography.h +++ b/src/gui/mrview/tool/tractography/tractography.h @@ -10,7 +10,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * For more details, see www.mrtrix.org - * + * */ #ifndef __gui_mrview_tool_tractography_h__ @@ -102,6 +102,7 @@ namespace MR TrackScalarFileOptions *scalar_file_options; LightingDock *lighting_dock; + QGroupBox* slab_group_box; QSlider* opacity_slider; QSlider* thickness_slider; From 39cde330c3060977376fe1b13ed2ee917d6a04c1 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Mon, 10 Apr 2017 14:27:52 +0300 Subject: [PATCH 007/139] Tidy up indentation to use 2 whitespaces instead of tabs --- .../tool/tractography/track_scalar_file.cpp | 108 ++++++----- .../tool/tractography/track_scalar_file.h | 18 +- .../mrview/tool/tractography/tractogram.cpp | 54 +++--- src/gui/mrview/tool/tractography/tractogram.h | 12 +- .../tool/tractography/tractogram_enums.h | 39 ++-- .../mrview/tool/tractography/tractography.cpp | 178 +++++++++--------- .../mrview/tool/tractography/tractography.h | 12 +- 7 files changed, 208 insertions(+), 213 deletions(-) diff --git a/src/gui/mrview/tool/tractography/track_scalar_file.cpp b/src/gui/mrview/tool/tractography/track_scalar_file.cpp index 113d9b2abb..4718e86dd5 100644 --- a/src/gui/mrview/tool/tractography/track_scalar_file.cpp +++ b/src/gui/mrview/tool/tractography/track_scalar_file.cpp @@ -1,22 +1,22 @@ /* * 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/tractography/track_scalar_file.h" #include "gui/dialog/file.h" #include "gui/mrview/colourmap.h" -#include "gui/mrview/tool/tractography/tractogram.h" +#include "gui/mrview/tool/tractography/tractogram.h" namespace MR @@ -247,24 +247,28 @@ namespace MR bool TrackScalarFileOptions::open_intensity_track_scalar_file_slot () { std::string scalar_file = Dialog::File::get_file (this, "Select scalar text file or Track Scalar file (.tsf) to open", ""); - return open_intensity_track_scalar_file_slot(scalar_file); + return open_intensity_track_scalar_file_slot(scalar_file); + } + + + + + bool TrackScalarFileOptions::open_intensity_track_scalar_file_slot(std::string scalar_file) + { + if (!scalar_file.empty()) { + try { + tractogram->load_intensity_track_scalars (scalar_file); + tractogram->set_color_type (TrackColourType::ScalarFile); + } + catch (Exception& E) { + E.display(); + scalar_file.clear(); + } + } + update_UI(); + window().updateGL(); + return scalar_file.size(); } - bool TrackScalarFileOptions::open_intensity_track_scalar_file_slot(std::string scalar_file) - { - if (!scalar_file.empty()) { - try { - tractogram->load_intensity_track_scalars(scalar_file); - tractogram->set_color_type(TrackColourType::ScalarFile); - } - catch (Exception& E) { - E.display(); - scalar_file.clear(); - } - } - update_UI(); - window().updateGL(); - return scalar_file.size(); - } void TrackScalarFileOptions::show_colour_bar_slot () { @@ -287,35 +291,35 @@ namespace MR } } - - - void TrackScalarFileOptions::set_threshold(GUI::MRView::Tool::TrackThresholdType dataSource, default_type min, default_type max)//TrackThresholdType dataSource - { - if (tractogram) { - //Source - tractogram->set_threshold_type(dataSource); - //Range - if (dataSource != TrackThresholdType::None) - { - tractogram->lessthan = min; - tractogram->greaterthan = max; - threshold_lower_box->setChecked(true); - threshold_upper_box->setChecked(true); - } - - update_UI(); - window().updateGL(); - } - } - - void TrackScalarFileOptions::set_scaling(default_type min, default_type max) - { - if (tractogram) { - tractogram->set_windowing(min,max); - update_UI(); - window().updateGL(); - } - } + + + void TrackScalarFileOptions::set_threshold(GUI::MRView::Tool::TrackThresholdType dataSource, default_type min, default_type max)//TrackThresholdType dataSource + { + if (tractogram) { + //Source + tractogram->set_threshold_type(dataSource); + //Range + if (dataSource != TrackThresholdType::None) + { + tractogram->lessthan = min; + tractogram->greaterthan = max; + threshold_lower_box->setChecked(true); + threshold_upper_box->setChecked(true); + } + + update_UI(); + window().updateGL(); + } + } + + void TrackScalarFileOptions::set_scaling(default_type min, default_type max) + { + if (tractogram) { + tractogram->set_windowing(min,max); + update_UI(); + window().updateGL(); + } + } void TrackScalarFileOptions::on_set_scaling_slot () { diff --git a/src/gui/mrview/tool/tractography/track_scalar_file.h b/src/gui/mrview/tool/tractography/track_scalar_file.h index 17c2cc35e7..d89c8037b5 100644 --- a/src/gui/mrview/tool/tractography/track_scalar_file.h +++ b/src/gui/mrview/tool/tractography/track_scalar_file.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 __gui_mrtrix_tools_tractography_scalar_file_options_h__ @@ -47,14 +47,14 @@ namespace MR void render_tractogram_colourbar (const Tool::Tractogram&) override; void update_UI(); - void set_scaling(default_type min, default_type max); - void set_threshold(GUI::MRView::Tool::TrackThresholdType dataSource, default_type min, default_type max); - + void set_scaling(default_type min, default_type max); + void set_threshold(GUI::MRView::Tool::TrackThresholdType dataSource, default_type min, default_type max); + public slots: bool open_intensity_track_scalar_file_slot (); - bool open_intensity_track_scalar_file_slot(std::string); - + bool open_intensity_track_scalar_file_slot(std::string); + private slots: void show_colour_bar_slot(); diff --git a/src/gui/mrview/tool/tractography/tractogram.cpp b/src/gui/mrview/tool/tractography/tractogram.cpp index a1e8c89f29..4ca3e173d8 100644 --- a/src/gui/mrview/tool/tractography/tractogram.cpp +++ b/src/gui/mrview/tool/tractography/tractogram.cpp @@ -1,17 +1,17 @@ /* * 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 "progressbar.h" #include "gui/mrview/tool/tractography/tractogram.h" @@ -40,7 +40,7 @@ namespace MR { const Tractogram& tractogram = dynamic_cast(displayable); - std::string source = + std::string source = "layout (location = 0) in vec3 vertex;\n" "layout (location = 1) in vec3 prev_vertex;\n" "layout (location = 2) in vec3 next_vertex;\n"; @@ -141,7 +141,7 @@ namespace MR source += "out vec3 g_tangent;\n"; if (color_type == TrackColourType::ScalarFile || color_type == TrackColourType::Ends) - source += + source += "in vec3 v_colour[];\n" "out vec3 fColour;\n"; @@ -152,8 +152,8 @@ namespace MR source += "void main() {\n"; - if (do_crop_to_slab) - source += + if (do_crop_to_slab) + source += " if (v_include[0] < 0.0 && v_include[1] < 0.0) return;\n" " if (v_include[0] > 1.0 && v_include[1] > 1.0) return;\n"; @@ -169,7 +169,7 @@ namespace MR if (use_lighting) source += " g_height = 0.0;\n"; - source += + source += " gl_Position = gl_in[0].gl_Position - vec4(v_end[0],0,0);\n" " EmitVertex();\n"; @@ -197,7 +197,7 @@ namespace MR if (use_lighting) source += " g_height = PI;\n"; - source += + source += " gl_Position = gl_in[1].gl_Position + vec4 (v_end[1],0,0);\n" " EmitVertex();\n" "}\n"; @@ -225,7 +225,7 @@ namespace MR source += "in float g_amp;\n"; if (use_lighting) - source += + source += "uniform float ambient, diffuse, specular, shine;\n" "uniform vec3 light_pos;\n" "in float g_height;\n"; @@ -305,7 +305,7 @@ namespace MR - void Tractogram::Shader::update (const Displayable& object) + void Tractogram::Shader::update (const Displayable& object) { const Tractogram& tractogram (dynamic_cast (object)); do_crop_to_slab = tractogram.tractography_tool.crop_to_slab(); @@ -579,10 +579,10 @@ namespace MR file.close(); ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; } - - - - + + + + void Tractogram::load_end_colours() { // These data are now retained in memory - no need to re-scan track file @@ -795,9 +795,9 @@ namespace MR ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; } - - - + + + void Tractogram::erase_colour_data() { MRView::GrabContext context; @@ -873,7 +873,7 @@ namespace MR void Tractogram::load_tracks_onto_GPU (std::vector& buffer, std::vector& starts, std::vector& sizes, - size_t& tck_count) + size_t& tck_count) { ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; @@ -900,12 +900,12 @@ namespace MR tck_count = 0; ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; } - - - - - - void Tractogram::load_end_colours_onto_GPU (std::vector& buffer) + + + + + + void Tractogram::load_end_colours_onto_GPU (std::vector& buffer) { ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; diff --git a/src/gui/mrview/tool/tractography/tractogram.h b/src/gui/mrview/tool/tractography/tractogram.h index 352582f8c3..cd26b7f4f8 100644 --- a/src/gui/mrview/tool/tractography/tractogram.h +++ b/src/gui/mrview/tool/tractography/tractogram.h @@ -1,19 +1,19 @@ /* * 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_tractogram_h__ +#ifndef __gui_mrview_tool_tractogram_h__ #define __gui_mrview_tool_tractogram_h__ //#include "gui/mrview/tool/tractography/tractogram_enums.h" @@ -152,7 +152,7 @@ namespace MR std::vector& starts, std::vector& sizes, size_t& tck_count); - + void load_end_colours_onto_GPU (std::vector&); void load_intensity_scalars_onto_GPU (std::vector& buffer); diff --git a/src/gui/mrview/tool/tractography/tractogram_enums.h b/src/gui/mrview/tool/tractography/tractogram_enums.h index 6b47528d3a..9149ef4749 100644 --- a/src/gui/mrview/tool/tractography/tractogram_enums.h +++ b/src/gui/mrview/tool/tractography/tractogram_enums.h @@ -1,43 +1,34 @@ /* * 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_tractogram_enums_h__ #define __gui_mrview_tool_tractogram_enums_h__ - -//#include "gui/mrview/displayable.h" -//#include "dwi/tractography/properties.h" -//#include "gui/mrview/tool/tractography/tractography.h" -//#include "gui/mrview/colourmap.h" - namespace MR { - - namespace GUI - { - - namespace MRView - { - namespace Tool - { - - enum class TrackColourType { Direction, Ends, Manual, ScalarFile }; - enum class TrackThresholdType { None, UseColourFile, SeparateFile }; - } - } - } + namespace GUI + { + namespace MRView + { + namespace Tool + { + enum class TrackColourType { Direction, Ends, Manual, ScalarFile }; + enum class TrackThresholdType { None, UseColourFile, SeparateFile }; + } + } + } } diff --git a/src/gui/mrview/tool/tractography/tractography.cpp b/src/gui/mrview/tool/tractography/tractography.cpp index 1213e9d2f5..88302d1064 100644 --- a/src/gui/mrview/tool/tractography/tractography.cpp +++ b/src/gui/mrview/tool/tractography/tractography.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 "mrtrix.h" @@ -213,7 +213,7 @@ namespace MR main_box->addWidget (general_groupbox, 0); - lighting = new GL::Lighting (parent); + lighting = new GL::Lighting (parent); lighting->diffuse = 0.8; lighting->shine = 5.0; connect (lighting, SIGNAL (changed()), SLOT (hide_all_slot())); @@ -336,7 +336,7 @@ namespace MR } - void Tractography::toggle_shown_slot (const QModelIndex& index, const QModelIndex& index2) + void Tractography::toggle_shown_slot (const QModelIndex& index, const QModelIndex& index2) { if (index.row() == index2.row()) { tractogram_list_view->setCurrentIndex(index); @@ -443,8 +443,8 @@ namespace MR update_scalar_options(); window().updateGL(); } - - + + void Tractography::colour_track_by_ends_slot() { QModelIndexList indices = tractogram_list_view->selectionModel()->selectedIndexes(); @@ -657,8 +657,8 @@ namespace MR - void Tractography::add_commandline_options (MR::App::OptionList& options) - { + void Tractography::add_commandline_options (MR::App::OptionList& options) + { using namespace MR::App; options + OptionGroup ("Tractography tool options") @@ -672,88 +672,88 @@ namespace MR + Option ("tractography.opacity", "Opacity of tractography display, [0.0, 1.0], default is 1.0.").allow_multiple() + Argument("value").type_float ( 0.0, 1.0 ) - + Option("tractography.slab", "Slab thickness of tractography display, in mm. -1 to turn off crop to slab.").allow_multiple() - + Argument("value").type_float(-1, 1e6) + + Option("tractography.slab", "Slab thickness of tractography display, in mm. -1 to turn off crop to slab.").allow_multiple() + + Argument("value").type_float(-1, 1e6) + Option ("tractography.tsf", "Load the track scalar file. Filename RangeMin,RangeMax,ThresholdMin,ThesholdMax (Range and threshold optional).").allow_multiple() + Argument("tsf").type_file_in() - + Argument("range").type_sequence_float().optional() + + Argument("range").type_sequence_float().optional() ; - + } - bool Tractography::process_commandline_option (const MR::App::ParsedOption& opt) + bool Tractography::process_commandline_option (const MR::App::ParsedOption& opt) { - if (opt.opt->is ("tractography.load")) - { + if (opt.opt->is ("tractography.load")) + { std::vector list (1, std::string(opt[0])); - try - { - tractogram_list_model->add_items (list, *this); - window().updateGL(); - return true; + try + { + tractogram_list_model->add_items (list, *this); + window().updateGL(); + return true; } catch (Exception& E) { E.display(); } return true; } - if (opt.opt->is("tractography.tsf")) - { - try - { - int count = tractogram_list_model->rowCount(); - if (count == 0) - { - QMessageBox::warning(QApplication::activeWindow(), - tr("Tractogram colour error"), - tr("TSF specified but no tractography loaded. Ensure TSF argument follows the tractography.load argument."), - QMessageBox::Ok, - QMessageBox::Ok); - } - else - { - //Select the last loaded tck file in the gui - QModelIndex index = tractogram_list_view->model()->index(count-1, 0); - tractogram_list_view->setCurrentIndex(index); - - //set its tsf filename and load the tsf file - Tractogram* tractogram = dynamic_cast(tractogram_list_model->items[index.row()].get()); - scalar_file_options->set_tractogram(tractogram); - scalar_file_options->open_intensity_track_scalar_file_slot(std::string(opt[0])); - - //Set the GUI to use the file for visualisation - colour_combobox->setCurrentIndex(4); // Set combobox to "File" - - //Set the visualisation range/threshold if supplied - if (opt.opt->size() > 1) - { - std::vector range = opt[1].as_sequence_float(); - - if (range.size() > 1) - { - //Range supplied - scalar_file_options->set_scaling(range[0], range[1]); - - if (range.size() > 3) - { - //Thresholds supplied - scalar_file_options->set_threshold(TrackThresholdType::UseColourFile, range[2], range[3]); - } - } - } - } - } - catch(Exception& E) { E.display(); } - - return true; - } + if (opt.opt->is("tractography.tsf")) + { + try + { + int count = tractogram_list_model->rowCount(); + if (count == 0) + { + QMessageBox::warning(QApplication::activeWindow(), + tr("Tractogram colour error"), + tr("TSF specified but no tractography loaded. Ensure TSF argument follows the tractography.load argument."), + QMessageBox::Ok, + QMessageBox::Ok); + } + else + { + //Select the last loaded tck file in the gui + QModelIndex index = tractogram_list_view->model()->index(count-1, 0); + tractogram_list_view->setCurrentIndex(index); + + //set its tsf filename and load the tsf file + Tractogram* tractogram = dynamic_cast(tractogram_list_model->items[index.row()].get()); + scalar_file_options->set_tractogram(tractogram); + scalar_file_options->open_intensity_track_scalar_file_slot(std::string(opt[0])); + + //Set the GUI to use the file for visualisation + colour_combobox->setCurrentIndex(4); // Set combobox to "File" + + //Set the visualisation range/threshold if supplied + if (opt.opt->size() > 1) + { + std::vector range = opt[1].as_sequence_float(); + + if (range.size() > 1) + { + //Range supplied + scalar_file_options->set_scaling(range[0], range[1]); + + if (range.size() > 3) + { + //Thresholds supplied + scalar_file_options->set_threshold(TrackThresholdType::UseColourFile, range[2], range[3]); + } + } + } + } + } + catch(Exception& E) { E.display(); } + + return true; + } if (opt.opt->is ("tractography.thickness")) { - // Thickness runs from -1000 to 1000, + // Thickness runs from -1000 to 1000, float thickness = float(opt[0]) * 1000.0f; - try { + try { thickness_slider->setValue(thickness); } catch (Exception& E) { E.display(); } @@ -770,21 +770,21 @@ namespace MR return true; } - if (opt.opt->is("tractography.slab")) { - float thickness = opt[0]; - try { - bool crop = thickness > 0; - slab_group_box->setChecked(crop); - on_crop_to_slab_slot(crop);//Needs to be manually bumped - if(crop) - { - slab_entry->setValue(thickness); - on_slab_thickness_slot();//Needs to be manually bumped - } - } - catch (Exception& E) { E.display(); } - return true; - } + if (opt.opt->is("tractography.slab")) { + float thickness = opt[0]; + try { + bool crop = thickness > 0; + slab_group_box->setChecked(crop); + on_crop_to_slab_slot(crop);//Needs to be manually bumped + if(crop) + { + slab_entry->setValue(thickness); + on_slab_thickness_slot();//Needs to be manually bumped + } + } + catch (Exception& E) { E.display(); } + return true; + } return false; } diff --git a/src/gui/mrview/tool/tractography/tractography.h b/src/gui/mrview/tool/tractography/tractography.h index e58acd95f6..e63490c198 100644 --- a/src/gui/mrview/tool/tractography/tractography.h +++ b/src/gui/mrview/tool/tractography/tractography.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 __gui_mrview_tool_tractography_h__ @@ -102,10 +102,10 @@ namespace MR TrackScalarFileOptions *scalar_file_options; LightingDock *lighting_dock; - QGroupBox* slab_group_box; + QGroupBox* slab_group_box; QSlider* opacity_slider; QSlider* thickness_slider; - + void dropEvent (QDropEvent* event) override; void update_scalar_options(); From c2d31dce91271b59ad5186390b5aa53e9cfc78c9 Mon Sep 17 00:00:00 2001 From: rei19q Date: Tue, 11 Apr 2017 18:11:03 +1000 Subject: [PATCH 008/139] Split tractography.tsf commandline arguments into three Error message now to stderr, not as window --- .../mrview/tool/tractography/tractography.cpp | 153 ++++++++++++------ .../mrview/tool/tractography/tractography.h | 3 + 2 files changed, 111 insertions(+), 45 deletions(-) diff --git a/src/gui/mrview/tool/tractography/tractography.cpp b/src/gui/mrview/tool/tractography/tractography.cpp index 88302d1064..158ad45315 100644 --- a/src/gui/mrview/tool/tractography/tractography.cpp +++ b/src/gui/mrview/tool/tractography/tractography.cpp @@ -675,13 +675,32 @@ namespace MR + Option("tractography.slab", "Slab thickness of tractography display, in mm. -1 to turn off crop to slab.").allow_multiple() + Argument("value").type_float(-1, 1e6) - + Option ("tractography.tsf", "Load the track scalar file. Filename RangeMin,RangeMax,ThresholdMin,ThesholdMax (Range and threshold optional).").allow_multiple() - + Argument("tsf").type_file_in() - + Argument("range").type_sequence_float().optional() + + Option ("tractography.tsf_load", "Load the specified tractography scalar file.").allow_multiple() + + Argument("tsf").type_file_in() + + + Option ("tractography.tsf_range", "Set range for the tractography scalar file. Requires tractography.tsf_load already provided. RangeMin,RangeMax").allow_multiple() + + Argument("range").type_sequence_float() + + + Option ("tractography.tsf_thresh", "Set thresholds for the tractography scalar file. Requires tractography.tsf_load already provided. ThresholdMin,ThesholdMax").allow_multiple() + + Argument("thresh").type_sequence_float() ; + } + /* + Selects the last tractogram in the tractogram_list_view and updates the window. If no tractograms are in the list view, no action is taken. + */ + void Tractography::select_last_added_tractogram() + { + int count = tractogram_list_model->rowCount(); + if(count != 0){ + QModelIndex index = tractogram_list_view->model()->index(count-1, 0); + tractogram_list_view->setCurrentIndex(index); + window().updateGL(); + } + } + bool Tractography::process_commandline_option (const MR::App::ParsedOption& opt) { @@ -691,7 +710,7 @@ namespace MR try { tractogram_list_model->add_items (list, *this); - window().updateGL(); + select_last_added_tractogram();//select track file in the gui. Req for tractography.tsf_* to work return true; } catch (Exception& E) { E.display(); } @@ -699,57 +718,65 @@ namespace MR } - if (opt.opt->is("tractography.tsf")) + if (opt.opt->is("tractography.tsf_load")) { try { - int count = tractogram_list_model->rowCount(); - if (count == 0) + + if(process_commandline_option_tsf_check_tracto_loaded()) { - QMessageBox::warning(QApplication::activeWindow(), - tr("Tractogram colour error"), - tr("TSF specified but no tractography loaded. Ensure TSF argument follows the tractography.load argument."), - QMessageBox::Ok, - QMessageBox::Ok); - } - else - { - //Select the last loaded tck file in the gui - QModelIndex index = tractogram_list_view->model()->index(count-1, 0); - tractogram_list_view->setCurrentIndex(index); - - //set its tsf filename and load the tsf file - Tractogram* tractogram = dynamic_cast(tractogram_list_model->items[index.row()].get()); - scalar_file_options->set_tractogram(tractogram); - scalar_file_options->open_intensity_track_scalar_file_slot(std::string(opt[0])); - - //Set the GUI to use the file for visualisation - colour_combobox->setCurrentIndex(4); // Set combobox to "File" - - //Set the visualisation range/threshold if supplied - if (opt.opt->size() > 1) - { - std::vector range = opt[1].as_sequence_float(); - - if (range.size() > 1) - { - //Range supplied - scalar_file_options->set_scaling(range[0], range[1]); - - if (range.size() > 3) - { - //Thresholds supplied - scalar_file_options->set_threshold(TrackThresholdType::UseColourFile, range[2], range[3]); - } - } + QModelIndexList indices = tractogram_list_view->selectionModel()->selectedIndexes(); + + if(indices.size() == 1) {//just in case future edits break this assumption + Tractogram* tractogram = tractogram_list_model->get_tractogram (indices[0]); + + //set its tsf filename and load the tsf file + scalar_file_options->set_tractogram(tractogram); + scalar_file_options->open_intensity_track_scalar_file_slot(std::string(opt[0])); + + //Set the GUI to use the file for visualisation + colour_combobox->setCurrentIndex(4); // Set combobox to "File" } - } + } } catch(Exception& E) { E.display(); } return true; } + if (opt.opt->is("tractography.tsf_range")) + { + try + { + //Set the tsf visualisation range + std::vector range; + if(process_commandline_option_tsf_option(opt,2, range)) + { + scalar_file_options->set_scaling(range[0], range[1]); + } + } + catch(Exception& E) { E.display(); } + return true; + } + + + if (opt.opt->is("tractography.tsf_thresh")) + { + try + { + //Set the tsf visualisation threshold + std::vector range; + if(process_commandline_option_tsf_option(opt,2, range)) + { + scalar_file_options->set_threshold(TrackThresholdType::UseColourFile,range[0], range[1]); + } + } + catch(Exception& E) { E.display(); } + return true; + } + + + if (opt.opt->is ("tractography.thickness")) { // Thickness runs from -1000 to 1000, float thickness = float(opt[0]) * 1000.0f; @@ -790,7 +817,43 @@ namespace MR } - + /*Checks whether any tractography has been loaded and warns the user if it has not*/ + bool Tractography::process_commandline_option_tsf_check_tracto_loaded() + { + int count = tractogram_list_model->rowCount(); + if (count == 0){ + //Error to std error to save many dialogs appearing for a single missed argument + std::cerr << "TSF argument specified but no tractography loaded. Ensure TSF arguments follow the tractography.load argument.\n"; + } + return count != 0; + } + + /*Checks whether legal to apply tsf options and prepares the scalar_file_options to do so. Returns the vector of floats parsed from the options, or null on fail*/ + bool Tractography::process_commandline_option_tsf_option(const MR::App::ParsedOption& opt, uint reqArgSize, std::vector& range) + { + if(process_commandline_option_tsf_check_tracto_loaded()){ + QModelIndexList indices = tractogram_list_view->selectionModel()->selectedIndexes(); + range = opt[0].as_sequence_float(); + if(indices.size() == 1 && range.size() == reqArgSize){ + //values supplied + Tractogram* tractogram = tractogram_list_model->get_tractogram (indices[0]); + if(tractogram->get_color_type() == TrackColourType::ScalarFile){ + //prereq options supplied/executed + scalar_file_options->set_tractogram(tractogram); + return true; + } + else + { + std::cerr << "Could not apply TSF argument - tractography.load_tsf not supplied.\n"; + } + } + else + { + std::cerr << "Could not apply TSF argument - insufficient number of arguments provided.\n"; + } + } + return false; + } } } } diff --git a/src/gui/mrview/tool/tractography/tractography.h b/src/gui/mrview/tool/tractography/tractography.h index e63490c198..ee85795107 100644 --- a/src/gui/mrview/tool/tractography/tractography.h +++ b/src/gui/mrview/tool/tractography/tractography.h @@ -108,6 +108,9 @@ namespace MR void dropEvent (QDropEvent* event) override; void update_scalar_options(); + void select_last_added_tractogram(); + bool process_commandline_option_tsf_check_tracto_loaded (); + bool process_commandline_option_tsf_option (const MR::App::ParsedOption&, uint, std::vector& range); }; } From 53e344882abb01eea2729e69281f314da7dbcf09 Mon Sep 17 00:00:00 2001 From: rei19q Date: Fri, 19 May 2017 08:58:42 +1000 Subject: [PATCH 009/139] MRView tractography tool: refactored open tractogram (through gui and command line commands) to call a single method --- .../mrview/tool/tractography/tractography.cpp | 19 +++++++++---------- .../mrview/tool/tractography/tractography.h | 1 + 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/gui/mrview/tool/tractography/tractography.cpp b/src/gui/mrview/tool/tractography/tractography.cpp index 158ad45315..5b613cac6d 100644 --- a/src/gui/mrview/tool/tractography/tractography.cpp +++ b/src/gui/mrview/tool/tractography/tractography.cpp @@ -288,18 +288,23 @@ namespace MR void Tractography::tractogram_open_slot () { std::vector list = Dialog::File::get_files (this, "Select tractograms to open", "Tractograms (*.tck)"); - if (list.empty()) + add_tractogram(list); + } + + void Tractography::add_tractogram(std::vector& list) + { + if (list.empty()) return; try { tractogram_list_model->add_items (list, *this); - window().updateGL(); + select_last_added_tractogram(); } catch (Exception& E) { E.display(); } + } - void Tractography::dropEvent (QDropEvent* event) { static constexpr int max_files = 32; @@ -707,13 +712,7 @@ namespace MR if (opt.opt->is ("tractography.load")) { std::vector list (1, std::string(opt[0])); - try - { - tractogram_list_model->add_items (list, *this); - select_last_added_tractogram();//select track file in the gui. Req for tractography.tsf_* to work - return true; - } - catch (Exception& E) { E.display(); } + add_tractogram(list); return true; } diff --git a/src/gui/mrview/tool/tractography/tractography.h b/src/gui/mrview/tool/tractography/tractography.h index ee85795107..894bbcdca8 100644 --- a/src/gui/mrview/tool/tractography/tractography.h +++ b/src/gui/mrview/tool/tractography/tractography.h @@ -108,6 +108,7 @@ namespace MR void dropEvent (QDropEvent* event) override; void update_scalar_options(); + void add_tractogram(std::vector& list); void select_last_added_tractogram(); bool process_commandline_option_tsf_check_tracto_loaded (); bool process_commandline_option_tsf_option (const MR::App::ParsedOption&, uint, std::vector& range); From 32abb92554c31289fc78081a91b214a95daf1662 Mon Sep 17 00:00:00 2001 From: rei19q Date: Fri, 19 May 2017 09:43:22 +1000 Subject: [PATCH 010/139] Fixed build errors introduced by merge with MRtrix3 main repo --- src/gui/mrview/tool/tractography/tractography.cpp | 6 +++--- src/gui/mrview/tool/tractography/tractography.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/mrview/tool/tractography/tractography.cpp b/src/gui/mrview/tool/tractography/tractography.cpp index 3b6562795c..5e999a0f9b 100644 --- a/src/gui/mrview/tool/tractography/tractography.cpp +++ b/src/gui/mrview/tool/tractography/tractography.cpp @@ -288,11 +288,11 @@ namespace MR void Tractography::tractogram_open_slot () { - std::vector list = Dialog::File::get_files (this, "Select tractograms to open", "Tractograms (*.tck)"); + MR::vector list = Dialog::File::get_files (this, "Select tractograms to open", "Tractograms (*.tck)"); add_tractogram(list); } - void Tractography::add_tractogram(std::vector& list) + void Tractography::add_tractogram(MR::vector& list) { if (list.empty()) { return; } @@ -712,7 +712,7 @@ namespace MR if (opt.opt->is ("tractography.load")) { - std::vector list (1, std::string(opt[0])); + MR::vector list (1, std::string(opt[0])); add_tractogram(list); return true; } diff --git a/src/gui/mrview/tool/tractography/tractography.h b/src/gui/mrview/tool/tractography/tractography.h index c8bb6b76a7..2abf80f4f5 100644 --- a/src/gui/mrview/tool/tractography/tractography.h +++ b/src/gui/mrview/tool/tractography/tractography.h @@ -107,7 +107,7 @@ namespace MR void dropEvent (QDropEvent* event) override; void update_scalar_options(); - void add_tractogram(std::vector& list); + void add_tractogram(vector& list); void select_last_added_tractogram(); bool process_commandline_option_tsf_check_tracto_loaded (); bool process_commandline_option_tsf_option (const MR::App::ParsedOption&, uint, std::vector& range); From da512aa53444cb6345574e497733c485f44267ab Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Fri, 19 May 2017 15:00:13 +0100 Subject: [PATCH 011/139] minor fixes for Eigen 3.3 compatibility --- src/gui/mrview/tool/tractography/tractography.cpp | 12 ++++++------ src/gui/mrview/tool/tractography/tractography.h | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/gui/mrview/tool/tractography/tractography.cpp b/src/gui/mrview/tool/tractography/tractography.cpp index 5e999a0f9b..14a77b23eb 100644 --- a/src/gui/mrview/tool/tractography/tractography.cpp +++ b/src/gui/mrview/tool/tractography/tractography.cpp @@ -41,7 +41,7 @@ namespace MR Model (QObject* parent) : ListModelBase (parent) { } - void add_items (vector& filenames, + void add_items (MR::vector& filenames, Tractography& tractography_tool) { for (size_t i = 0; i < filenames.size(); ++i) { @@ -292,7 +292,7 @@ namespace MR add_tractogram(list); } - void Tractography::add_tractogram(MR::vector& list) + void Tractography::add_tractogram (MR::vector& list) { if (list.empty()) { return; } @@ -312,7 +312,7 @@ namespace MR const QMimeData* mimeData = event->mimeData(); if (mimeData->hasUrls()) { - vector list; + MR::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()); @@ -749,7 +749,7 @@ namespace MR try { //Set the tsf visualisation range - std::vector range; + MR::vector range; if(process_commandline_option_tsf_option(opt,2, range)) { scalar_file_options->set_scaling(range[0], range[1]); @@ -765,7 +765,7 @@ namespace MR try { //Set the tsf visualisation threshold - std::vector range; + MR::vector range; if(process_commandline_option_tsf_option(opt,2, range)) { scalar_file_options->set_threshold(TrackThresholdType::UseColourFile,range[0], range[1]); @@ -829,7 +829,7 @@ namespace MR } /*Checks whether legal to apply tsf options and prepares the scalar_file_options to do so. Returns the vector of floats parsed from the options, or null on fail*/ - bool Tractography::process_commandline_option_tsf_option(const MR::App::ParsedOption& opt, uint reqArgSize, std::vector& range) + bool Tractography::process_commandline_option_tsf_option(const MR::App::ParsedOption& opt, uint reqArgSize, MR::vector& range) { if(process_commandline_option_tsf_check_tracto_loaded()){ QModelIndexList indices = tractogram_list_view->selectionModel()->selectedIndexes(); diff --git a/src/gui/mrview/tool/tractography/tractography.h b/src/gui/mrview/tool/tractography/tractography.h index 2abf80f4f5..fa1d452466 100644 --- a/src/gui/mrview/tool/tractography/tractography.h +++ b/src/gui/mrview/tool/tractography/tractography.h @@ -107,10 +107,10 @@ namespace MR void dropEvent (QDropEvent* event) override; void update_scalar_options(); - void add_tractogram(vector& list); + void add_tractogram (MR::vector& list); void select_last_added_tractogram(); bool process_commandline_option_tsf_check_tracto_loaded (); - bool process_commandline_option_tsf_option (const MR::App::ParsedOption&, uint, std::vector& range); + bool process_commandline_option_tsf_option (const MR::App::ParsedOption&, uint, MR::vector& range); }; } From 3db1bb2b07b2cbeed35596a63c67bdd310a0492d Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sat, 20 May 2017 01:08:36 +1000 Subject: [PATCH 012/139] Remove #include usages This header should no longer be included directly, since class MR::vector (defined in core/types.h) should always be used. --- cmd/connectome2tck.cpp | 9 +++------ cmd/connectomestats.cpp | 3 +-- cmd/label2colour.cpp | 1 - cmd/label2mesh.cpp | 4 ++-- cmd/mredit.cpp | 3 ++- cmd/mrhistmatch.cpp | 2 +- cmd/mrinfo.cpp | 2 +- cmd/mrmath.cpp | 5 +++-- cmd/mrstats.cpp | 6 ++++-- cmd/mrthreshold.cpp | 2 +- cmd/shbasis.cpp | 9 ++++----- cmd/tck2connectome.cpp | 2 +- cmd/tckedit.cpp | 3 +-- cmd/tckmap.cpp | 7 +++---- cmd/tcksift.cpp | 4 +--- cmd/tckstats.cpp | 5 ++--- cmd/vectorstats.cpp | 3 +-- core/algo/histogram.h | 2 +- core/algo/iterator.h | 2 -- core/algo/neighbourhooditerator.h | 3 +-- core/app.h | 6 +++--- core/cmdline_option.h | 4 ++-- core/exception.h | 3 +-- core/file/dicom/element.h | 2 +- core/file/json_utils.cpp | 2 +- core/file/nifti_utils.h | 2 -- core/formats/mrtrix_utils.h | 4 ++-- core/image_io/base.h | 4 ++-- core/image_io/scratch.cpp | 1 - core/math/Sn_scale_estimator.h | 2 -- core/math/median.h | 4 +--- core/math/stats/permutation.h | 3 +-- core/mrtrix.h | 1 - .../fixel_based_analysis/common_fba_steps/statistics.rst | 4 ++-- .../ismrm_hcp_tutorial.rst | 4 ++++ src/connectome/enhance.h | 2 +- src/connectome/lut.h | 6 +++--- src/dwi/directions/set.cpp | 1 - src/dwi/directions/set.h | 4 ++-- src/dwi/sdeconv/msmt_csd.h | 3 +-- src/dwi/shells.h | 2 +- src/dwi/tractography/GT/energy.h | 2 -- src/dwi/tractography/GT/gt.h | 2 +- src/dwi/tractography/GT/internalenergy.h | 2 +- src/dwi/tractography/GT/spatiallock.h | 3 ++- src/dwi/tractography/SIFT/gradient_sort.h | 3 ++- src/dwi/tractography/SIFT/model.h | 6 +++--- src/dwi/tractography/SIFT/sifter.h | 6 ++++-- src/dwi/tractography/SIFT2/coeff_optimiser.h | 2 -- src/dwi/tractography/SIFT2/fixel_updater.h | 2 +- src/dwi/tractography/SIFT2/line_search.h | 2 +- src/dwi/tractography/connectome/matrix.h | 3 ++- src/dwi/tractography/connectome/metric.h | 3 +-- src/dwi/tractography/connectome/tck2nodes.h | 3 +-- src/dwi/tractography/editing/loader.h | 3 ++- src/dwi/tractography/editing/worker.h | 3 ++- src/dwi/tractography/file.h | 1 - src/dwi/tractography/mapping/buffer_scratch_dump.h | 6 ++++-- src/dwi/tractography/mapping/mapper.h | 6 ++---- src/dwi/tractography/mapping/mapper_plugins.h | 3 +-- src/dwi/tractography/mapping/mapping.h | 4 ++-- src/dwi/tractography/resampling/downsampler.h | 2 -- src/dwi/tractography/resampling/resampling.cpp | 2 ++ src/dwi/tractography/resampling/resampling.h | 2 -- src/dwi/tractography/resampling/upsampler.h | 2 -- src/dwi/tractography/seeding/list.h | 5 ++--- src/dwi/tractography/streamline.h | 2 -- src/dwi/tractography/tracking/generated_track.h | 2 +- src/dwi/tractography/tracking/shared.h | 1 - src/dwi/tractography/tracking/write_kernel.h | 3 +-- src/gui/mrview/tool/connectome/connectome.h | 2 +- src/gui/mrview/tool/connectome/node.cpp | 3 +-- src/gui/mrview/tool/connectome/node_overlay.cpp | 3 ++- src/gui/mrview/tool/odf/model.h | 3 ++- src/gui/mrview/tool/roi_editor/item.cpp | 1 - src/gui/mrview/tool/roi_editor/item.h | 2 +- src/gui/mrview/tool/roi_editor/roi.h | 3 +-- src/gui/mrview/tool/roi_editor/undoentry.cpp | 1 - src/gui/mrview/tool/roi_editor/undoentry.h | 3 ++- src/gui/shapes/cylinder.cpp | 2 +- src/gui/shapes/cylinder.h | 2 -- src/gui/shapes/halfsphere.h | 2 +- src/gui/shapes/sphere.h | 2 +- src/registration/linear.h | 5 +++-- src/registration/nonlinear.h | 3 ++- src/registration/transform/search.h | 9 +++++---- src/stats/permstack.h | 2 +- src/surface/algo/image2mesh.h | 2 +- src/surface/algo/mesh2image.cpp | 2 +- src/surface/mesh.cpp | 3 ++- src/surface/mesh.h | 3 +-- src/surface/mesh_multi.h | 2 +- 92 files changed, 129 insertions(+), 158 deletions(-) diff --git a/cmd/connectome2tck.cpp b/cmd/connectome2tck.cpp index 47e7a52cd8..6dc73d0ca4 100644 --- a/cmd/connectome2tck.cpp +++ b/cmd/connectome2tck.cpp @@ -14,10 +14,12 @@ #include #include -#include #include "command.h" +#include "image.h" #include "progressbar.h" +#include "thread_queue.h" +#include "types.h" #include "connectome/connectome.h" @@ -28,11 +30,6 @@ #include "dwi/tractography/connectome/streamline.h" #include "dwi/tractography/mapping/loader.h" -#include "image.h" - -#include "thread_queue.h" - - using namespace MR; diff --git a/cmd/connectomestats.cpp b/cmd/connectomestats.cpp index 84094bf62a..f3bc064525 100644 --- a/cmd/connectomestats.cpp +++ b/cmd/connectomestats.cpp @@ -12,10 +12,9 @@ */ -#include - #include "command.h" #include "progressbar.h" +#include "types.h" #include "file/path.h" #include "math/stats/glm.h" diff --git a/cmd/label2colour.cpp b/cmd/label2colour.cpp index 15d97a7124..a18cd9efb5 100644 --- a/cmd/label2colour.cpp +++ b/cmd/label2colour.cpp @@ -13,7 +13,6 @@ #include -#include #include "command.h" #include "image.h" diff --git a/cmd/label2mesh.cpp b/cmd/label2mesh.cpp index d8623d7efa..8264adb2bc 100644 --- a/cmd/label2mesh.cpp +++ b/cmd/label2mesh.cpp @@ -13,13 +13,13 @@ #include -#include #include "command.h" +#include "image.h" #include "progressbar.h" #include "thread_queue.h" +#include "types.h" -#include "image.h" #include "algo/loop.h" #include "adapter/subset.h" diff --git a/cmd/mredit.cpp b/cmd/mredit.cpp index 2e8c12aaed..471535e19e 100644 --- a/cmd/mredit.cpp +++ b/cmd/mredit.cpp @@ -13,12 +13,13 @@ #include -#include #include "command.h" #include "image.h" #include "image_helpers.h" #include "transform.h" +#include "types.h" + #include "algo/copy.h" using namespace MR; diff --git a/cmd/mrhistmatch.cpp b/cmd/mrhistmatch.cpp index c5104d08a1..3b49588ec6 100644 --- a/cmd/mrhistmatch.cpp +++ b/cmd/mrhistmatch.cpp @@ -14,12 +14,12 @@ #include #include -#include #include "command.h" #include "datatype.h" #include "header.h" #include "image.h" + #include "algo/histogram.h" #include "algo/loop.h" diff --git a/cmd/mrinfo.cpp b/cmd/mrinfo.cpp index 23ef14d6eb..40ac83da44 100644 --- a/cmd/mrinfo.cpp +++ b/cmd/mrinfo.cpp @@ -14,11 +14,11 @@ #include #include -#include #include "command.h" #include "header.h" #include "phase_encoding.h" +#include "types.h" #include "file/json.h" #include "dwi/gradient.h" diff --git a/cmd/mrmath.cpp b/cmd/mrmath.cpp index 2915b8f0f5..5201ceb505 100644 --- a/cmd/mrmath.cpp +++ b/cmd/mrmath.cpp @@ -12,16 +12,17 @@ */ +#include + #include "command.h" #include "progressbar.h" #include "memory.h" #include "image.h" +#include "types.h" #include "algo/threaded_loop.h" #include "math/math.h" #include "math/median.h" -#include -#include using namespace MR; diff --git a/cmd/mrstats.cpp b/cmd/mrstats.cpp index 94cc35dd43..1f8b948cc4 100644 --- a/cmd/mrstats.cpp +++ b/cmd/mrstats.cpp @@ -13,17 +13,19 @@ #include -#include #include "command.h" #include "datatype.h" #include "image.h" #include "image_helpers.h" #include "memory.h" +#include "stats.h" +#include "types.h" + #include "algo/histogram.h" #include "algo/loop.h" #include "file/ofstream.h" -#include "stats.h" + using namespace MR; diff --git a/cmd/mrthreshold.cpp b/cmd/mrthreshold.cpp index 35a6033b87..54a940d1d5 100644 --- a/cmd/mrthreshold.cpp +++ b/cmd/mrthreshold.cpp @@ -13,13 +13,13 @@ #include -#include #include "command.h" #include "image.h" #include "image_helpers.h" #include "memory.h" #include "progressbar.h" +#include "types.h" #include "algo/loop.h" #include "filter/optimal_threshold.h" diff --git a/cmd/shbasis.cpp b/cmd/shbasis.cpp index 78d3019cca..8702fe660f 100644 --- a/cmd/shbasis.cpp +++ b/cmd/shbasis.cpp @@ -12,17 +12,16 @@ */ -#include - #include "app.h" #include "bitset.h" #include "command.h" #include "datatype.h" -#include "progressbar.h" -#include "memory.h" - #include "header.h" #include "image.h" +#include "memory.h" +#include "progressbar.h" +#include "types.h" + #include "algo/loop.h" #include "math/SH.h" diff --git a/cmd/tck2connectome.cpp b/cmd/tck2connectome.cpp index 56ac1219db..900137b5d6 100644 --- a/cmd/tck2connectome.cpp +++ b/cmd/tck2connectome.cpp @@ -12,12 +12,12 @@ */ -#include #include #include "command.h" #include "image.h" #include "thread_queue.h" +#include "types.h" #include "dwi/tractography/file.h" #include "dwi/tractography/properties.h" diff --git a/cmd/tckedit.cpp b/cmd/tckedit.cpp index 30b388f539..664dcdcc51 100644 --- a/cmd/tckedit.cpp +++ b/cmd/tckedit.cpp @@ -13,13 +13,12 @@ #include -#include #include "command.h" #include "exception.h" #include "mrtrix.h" - #include "thread_queue.h" +#include "types.h" #include "dwi/tractography/file.h" #include "dwi/tractography/properties.h" diff --git a/cmd/tckmap.cpp b/cmd/tckmap.cpp index ee08c1a0ff..8939827bf2 100644 --- a/cmd/tckmap.cpp +++ b/cmd/tckmap.cpp @@ -12,15 +12,14 @@ */ -#include #include #include "command.h" -#include "progressbar.h" -#include "memory.h" - #include "image.h" +#include "memory.h" +#include "progressbar.h" #include "thread_queue.h" +#include "types.h" #include "dwi/gradient.h" #include "dwi/tractography/file.h" diff --git a/cmd/tcksift.cpp b/cmd/tcksift.cpp index 26dbfc8b13..ab625897eb 100644 --- a/cmd/tcksift.cpp +++ b/cmd/tcksift.cpp @@ -12,11 +12,9 @@ */ -#include - #include "command.h" - #include "image.h" +#include "types.h" #include "math/SH.h" diff --git a/cmd/tckstats.cpp b/cmd/tckstats.cpp index cb7b44d23c..4821c96b53 100644 --- a/cmd/tckstats.cpp +++ b/cmd/tckstats.cpp @@ -12,11 +12,10 @@ */ -#include - #include "command.h" -#include "progressbar.h" #include "memory.h" +#include "progressbar.h" +#include "types.h" #include "file/ofstream.h" diff --git a/cmd/vectorstats.cpp b/cmd/vectorstats.cpp index 88a678c00e..4eaea1d94f 100644 --- a/cmd/vectorstats.cpp +++ b/cmd/vectorstats.cpp @@ -12,10 +12,9 @@ */ -#include - #include "command.h" #include "progressbar.h" +#include "types.h" #include "file/path.h" #include "math/stats/glm.h" diff --git a/core/algo/histogram.h b/core/algo/histogram.h index 2379c51e3c..f1c6f0ebd5 100644 --- a/core/algo/histogram.h +++ b/core/algo/histogram.h @@ -15,10 +15,10 @@ #ifndef __algo_histogram_h__ #define __algo_histogram_h__ -#include #include #include "image_helpers.h" +#include "types.h" #include "algo/loop.h" namespace MR diff --git a/core/algo/iterator.h b/core/algo/iterator.h index 358d820be5..9bcb62d8f8 100644 --- a/core/algo/iterator.h +++ b/core/algo/iterator.h @@ -15,8 +15,6 @@ #ifndef __algo_iterator_h__ #define __algo_iterator_h__ -#include - #include "types.h" namespace MR diff --git a/core/algo/neighbourhooditerator.h b/core/algo/neighbourhooditerator.h index 84fc787f2a..0e96d2897a 100644 --- a/core/algo/neighbourhooditerator.h +++ b/core/algo/neighbourhooditerator.h @@ -15,9 +15,8 @@ #ifndef __algo_neighbourhooditerator_h__ #define __algo_neighbourhooditerator_h__ -#include -#include "algo/iterator.h" #include "types.h" +#include "algo/iterator.h" namespace MR { diff --git a/core/app.h b/core/app.h index 10ccf60439..9b6877026d 100644 --- a/core/app.h +++ b/core/app.h @@ -16,17 +16,17 @@ #define __app_h__ #include -#include -#include #include +#include #ifdef None # undef None #endif #include "cmdline_option.h" -#include "file/path.h" #include "signal_handler.h" +#include "types.h" +#include "file/path.h" extern void usage (); diff --git a/core/cmdline_option.h b/core/cmdline_option.h index 927bf95655..3579e98f3e 100644 --- a/core/cmdline_option.h +++ b/core/cmdline_option.h @@ -16,15 +16,15 @@ #define __cmdline_option_h__ #include -#include -#include #include +#include #ifdef None # undef None #endif #include "mrtrix.h" +#include "types.h" namespace MR { diff --git a/core/exception.h b/core/exception.h index 50b37d48a6..53d64ab015 100644 --- a/core/exception.h +++ b/core/exception.h @@ -16,9 +16,8 @@ #define __mrtrix_exception_h__ #include -#include -#include #include +#include #include "types.h" diff --git a/core/file/dicom/element.h b/core/file/dicom/element.h index 50c34b747f..17b4b60baa 100644 --- a/core/file/dicom/element.h +++ b/core/file/dicom/element.h @@ -15,11 +15,11 @@ #ifndef __file_dicom_element_h__ #define __file_dicom_element_h__ -#include #include #include "memory.h" #include "raw.h" +#include "types.h" #include "file/mmap.h" #include "file/dicom/definitions.h" diff --git a/core/file/json_utils.cpp b/core/file/json_utils.cpp index 50053a789d..cd9baecd1a 100644 --- a/core/file/json_utils.cpp +++ b/core/file/json_utils.cpp @@ -13,7 +13,6 @@ #include -#include #include "file/json_utils.h" #include "file/nifti_utils.h" @@ -22,6 +21,7 @@ #include "header.h" #include "mrtrix.h" #include "phase_encoding.h" +#include "types.h" #include "file/ofstream.h" namespace MR diff --git a/core/file/nifti_utils.h b/core/file/nifti_utils.h index ba232f38f9..b5b2d93985 100644 --- a/core/file/nifti_utils.h +++ b/core/file/nifti_utils.h @@ -15,8 +15,6 @@ #ifndef __file_nifti_utils_h__ #define __file_nifti_utils_h__ -#include - #include "types.h" namespace MR diff --git a/core/formats/mrtrix_utils.h b/core/formats/mrtrix_utils.h index 0d97ec774d..22c1ead8ea 100644 --- a/core/formats/mrtrix_utils.h +++ b/core/formats/mrtrix_utils.h @@ -15,9 +15,9 @@ #ifndef __formats_mrtrix_utils_h__ #define __formats_mrtrix_utils_h__ -#include - #include "header.h" +#include "types.h" + #include "file/gz.h" #include "file/key_value.h" #include "file/ofstream.h" diff --git a/core/image_io/base.h b/core/image_io/base.h index 033919a7fb..a7d66bb30a 100644 --- a/core/image_io/base.h +++ b/core/image_io/base.h @@ -15,13 +15,13 @@ #ifndef __image_io_base_h__ #define __image_io_base_h__ -#include +#include #include #include -#include #include "memory.h" #include "mrtrix.h" +#include "types.h" #include "file/entry.h" #define MAX_FILES_PER_IMAGE 256U diff --git a/core/image_io/scratch.cpp b/core/image_io/scratch.cpp index 4347f7d8a8..04a6933f79 100644 --- a/core/image_io/scratch.cpp +++ b/core/image_io/scratch.cpp @@ -13,7 +13,6 @@ #include -#include #include "image_io/scratch.h" #include "header.h" diff --git a/core/math/Sn_scale_estimator.h b/core/math/Sn_scale_estimator.h index 95a2160008..d358014a82 100644 --- a/core/math/Sn_scale_estimator.h +++ b/core/math/Sn_scale_estimator.h @@ -15,8 +15,6 @@ #ifndef __math_Sn_scale_estimator_h__ #define __math_Sn_scale_estimator_h__ -#include - #include "types.h" #include "math/median.h" diff --git a/core/math/median.h b/core/math/median.h index ad255e4d04..831e5ccbc0 100644 --- a/core/math/median.h +++ b/core/math/median.h @@ -15,10 +15,8 @@ #ifndef __math_median_h__ #define __math_median_h__ -#include -#include - #include +#include #include "types.h" diff --git a/core/math/stats/permutation.h b/core/math/stats/permutation.h index bc4019e9b7..d9d6c9d8d9 100644 --- a/core/math/stats/permutation.h +++ b/core/math/stats/permutation.h @@ -15,8 +15,7 @@ #ifndef __math_stats_permutation_h__ #define __math_stats_permutation_h__ -#include - +#include "types.h" #include "math/stats/typedefs.h" namespace MR diff --git a/core/mrtrix.h b/core/mrtrix.h index 56094d687a..70f437e307 100644 --- a/core/mrtrix.h +++ b/core/mrtrix.h @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include diff --git a/docs/fixel_based_analysis/common_fba_steps/statistics.rst b/docs/fixel_based_analysis/common_fba_steps/statistics.rst index 4a221a2d21..19d65c9ca9 100644 --- a/docs/fixel_based_analysis/common_fba_steps/statistics.rst +++ b/docs/fixel_based_analysis/common_fba_steps/statistics.rst @@ -4,6 +4,6 @@ Statistical analysis is performed using `connectivity-based fixel enhancement #include -#include #include "bitset.h" +#include "types.h" #include "connectome/mat2vec.h" #include "stats/enhance.h" diff --git a/src/connectome/lut.h b/src/connectome/lut.h index b77b80d826..bb6671a249 100644 --- a/src/connectome/lut.h +++ b/src/connectome/lut.h @@ -15,14 +15,14 @@ #ifndef __connectome_lut_h__ #define __connectome_lut_h__ +#include +#include #include "app.h" +#include "types.h" #include "connectome/connectome.h" -#include -#include -#include namespace MR { diff --git a/src/dwi/directions/set.cpp b/src/dwi/directions/set.cpp index e7ec06bc23..a7e77c975d 100644 --- a/src/dwi/directions/set.cpp +++ b/src/dwi/directions/set.cpp @@ -16,7 +16,6 @@ #include #include -#include #include "bitset.h" #include "math/rng.h" diff --git a/src/dwi/directions/set.h b/src/dwi/directions/set.h index feebbaf64e..8ed73a5f3b 100644 --- a/src/dwi/directions/set.h +++ b/src/dwi/directions/set.h @@ -16,13 +16,13 @@ #define __dwi_directions_set_h__ - #include -#include #include "progressbar.h" +#include "types.h" #include "dwi/directions/predefined.h" + namespace MR { namespace DWI { namespace Directions { diff --git a/src/dwi/sdeconv/msmt_csd.h b/src/dwi/sdeconv/msmt_csd.h index fa929ff002..df4e68d239 100644 --- a/src/dwi/sdeconv/msmt_csd.h +++ b/src/dwi/sdeconv/msmt_csd.h @@ -15,9 +15,8 @@ #ifndef __dwi_sdeconv_msmt_csd_h__ #define __dwi_sdeconv_msmt_csd_h__ -#include - #include "header.h" +#include "types.h" #include "math/constrained_least_squares.h" #include "math/math.h" diff --git a/src/dwi/shells.h b/src/dwi/shells.h index ba673cdbe2..cc9a578cd0 100644 --- a/src/dwi/shells.h +++ b/src/dwi/shells.h @@ -18,10 +18,10 @@ #include #include -#include #include "app.h" #include "bitset.h" +#include "types.h" #include "file/config.h" diff --git a/src/dwi/tractography/GT/energy.h b/src/dwi/tractography/GT/energy.h index 40e3485a52..837e4ed486 100644 --- a/src/dwi/tractography/GT/energy.h +++ b/src/dwi/tractography/GT/energy.h @@ -15,8 +15,6 @@ #ifndef __gt_energy_h__ #define __gt_energy_h__ -#include - #include "dwi/tractography/GT/particle.h" #include "dwi/tractography/GT/gt.h" diff --git a/src/dwi/tractography/GT/gt.h b/src/dwi/tractography/GT/gt.h index 2eb67f39c2..b026773587 100644 --- a/src/dwi/tractography/GT/gt.h +++ b/src/dwi/tractography/GT/gt.h @@ -20,12 +20,12 @@ #define FRAC_PHASEOUT 10 #include -#include #include #include #include "progressbar.h" +#include "types.h" namespace MR { diff --git a/src/dwi/tractography/GT/internalenergy.h b/src/dwi/tractography/GT/internalenergy.h index f2bcfdd5b8..105cf2f44c 100644 --- a/src/dwi/tractography/GT/internalenergy.h +++ b/src/dwi/tractography/GT/internalenergy.h @@ -15,7 +15,7 @@ #ifndef __gt_internalenergy_h__ #define __gt_internalenergy_h__ -#include +#include "types.h" #include "math/math.h" #include "math/rng.h" diff --git a/src/dwi/tractography/GT/spatiallock.h b/src/dwi/tractography/GT/spatiallock.h index 64fd78c7db..255b8615a9 100644 --- a/src/dwi/tractography/GT/spatiallock.h +++ b/src/dwi/tractography/GT/spatiallock.h @@ -17,7 +17,8 @@ #include #include -#include + +#include "types.h" namespace MR { diff --git a/src/dwi/tractography/SIFT/gradient_sort.h b/src/dwi/tractography/SIFT/gradient_sort.h index fbb23c6e2e..d2a808e2d7 100644 --- a/src/dwi/tractography/SIFT/gradient_sort.h +++ b/src/dwi/tractography/SIFT/gradient_sort.h @@ -17,7 +17,8 @@ #include -#include + +#include "types.h" #include "dwi/tractography/SIFT/track_index_range.h" #include "dwi/tractography/SIFT/types.h" diff --git a/src/dwi/tractography/SIFT/model.h b/src/dwi/tractography/SIFT/model.h index aa497b40b3..7aff2e6e61 100644 --- a/src/dwi/tractography/SIFT/model.h +++ b/src/dwi/tractography/SIFT/model.h @@ -16,9 +16,9 @@ #define __dwi_tractography_sift_model_h__ -#include - #include "app.h" +#include "thread_queue.h" +#include "types.h" #include "dwi/fixel_map.h" @@ -38,7 +38,7 @@ #include "dwi/tractography/SIFT/track_index_range.h" #include "dwi/tractography/SIFT/types.h" -#include "thread_queue.h" + diff --git a/src/dwi/tractography/SIFT/sifter.h b/src/dwi/tractography/SIFT/sifter.h index e2fe263c03..af365e77a7 100644 --- a/src/dwi/tractography/SIFT/sifter.h +++ b/src/dwi/tractography/SIFT/sifter.h @@ -16,10 +16,12 @@ #define __dwi_tractography_sift_sifter_h__ -#include -#include "math/rng.h" #include "image.h" +#include "types.h" + +#include "math/rng.h" + #include "dwi/fixel_map.h" #include "dwi/directions/set.h" #include "dwi/tractography/SIFT/fixel.h" diff --git a/src/dwi/tractography/SIFT2/coeff_optimiser.h b/src/dwi/tractography/SIFT2/coeff_optimiser.h index e23de78559..82ccc1d8e7 100644 --- a/src/dwi/tractography/SIFT2/coeff_optimiser.h +++ b/src/dwi/tractography/SIFT2/coeff_optimiser.h @@ -16,8 +16,6 @@ #define __dwi_tractography_sift2_coeff_optimiser_h__ -#include - #include "bitset.h" #include "math/golden_section_search.h" diff --git a/src/dwi/tractography/SIFT2/fixel_updater.h b/src/dwi/tractography/SIFT2/fixel_updater.h index 78f2ceab1e..6db1ec50c5 100644 --- a/src/dwi/tractography/SIFT2/fixel_updater.h +++ b/src/dwi/tractography/SIFT2/fixel_updater.h @@ -16,7 +16,7 @@ #define __dwi_tractography_sift2_fixel_updater_h__ -#include +#include "types.h" #include "dwi/tractography/SIFT/track_index_range.h" #include "dwi/tractography/SIFT/types.h" diff --git a/src/dwi/tractography/SIFT2/line_search.h b/src/dwi/tractography/SIFT2/line_search.h index 5db06a0392..42e9b0aacb 100644 --- a/src/dwi/tractography/SIFT2/line_search.h +++ b/src/dwi/tractography/SIFT2/line_search.h @@ -16,7 +16,7 @@ #define __dwi_tractography_sift2_line_search_h__ -#include +#include "types.h" #include "dwi/tractography/SIFT/track_contribution.h" #include "dwi/tractography/SIFT/types.h" diff --git a/src/dwi/tractography/connectome/matrix.h b/src/dwi/tractography/connectome/matrix.h index 4a1341f1bc..d6a62dadbc 100644 --- a/src/dwi/tractography/connectome/matrix.h +++ b/src/dwi/tractography/connectome/matrix.h @@ -16,7 +16,8 @@ #define __dwi_tractography_connectome_matrix_h__ #include -#include + +#include "types.h" #include "connectome/connectome.h" #include "connectome/mat2vec.h" diff --git a/src/dwi/tractography/connectome/metric.h b/src/dwi/tractography/connectome/metric.h index 324ab0c588..c1f1c208d2 100644 --- a/src/dwi/tractography/connectome/metric.h +++ b/src/dwi/tractography/connectome/metric.h @@ -16,9 +16,8 @@ #define __dwi_tractography_connectome_metric_h__ -#include - #include "image.h" +#include "types.h" #include "algo/loop.h" #include "interp/linear.h" diff --git a/src/dwi/tractography/connectome/tck2nodes.h b/src/dwi/tractography/connectome/tck2nodes.h index d02ce2a7e5..03aee27730 100644 --- a/src/dwi/tractography/connectome/tck2nodes.h +++ b/src/dwi/tractography/connectome/tck2nodes.h @@ -16,9 +16,8 @@ #define __dwi_tractography_connectome_tck2nodes_h__ -#include - #include "image.h" +#include "types.h" #include "interp/linear.h" #include "interp/nearest.h" diff --git a/src/dwi/tractography/editing/loader.h b/src/dwi/tractography/editing/loader.h index b3f51bd817..e9c29884ba 100644 --- a/src/dwi/tractography/editing/loader.h +++ b/src/dwi/tractography/editing/loader.h @@ -17,9 +17,10 @@ #include -#include #include "memory.h" +#include "types.h" + #include "dwi/tractography/file.h" #include "dwi/tractography/properties.h" #include "dwi/tractography/streamline.h" diff --git a/src/dwi/tractography/editing/worker.h b/src/dwi/tractography/editing/worker.h index 65cfb5b2c2..0bb80c9b3f 100644 --- a/src/dwi/tractography/editing/worker.h +++ b/src/dwi/tractography/editing/worker.h @@ -17,7 +17,8 @@ #include -#include + +#include "types.h" #include "dwi/tractography/properties.h" #include "dwi/tractography/streamline.h" diff --git a/src/dwi/tractography/file.h b/src/dwi/tractography/file.h index f2b03039e2..0ddfa99179 100644 --- a/src/dwi/tractography/file.h +++ b/src/dwi/tractography/file.h @@ -16,7 +16,6 @@ #define __dwi_tractography_file_h__ #include -#include #include "app.h" #include "types.h" diff --git a/src/dwi/tractography/mapping/buffer_scratch_dump.h b/src/dwi/tractography/mapping/buffer_scratch_dump.h index cbfab95b0c..f6de0a378c 100644 --- a/src/dwi/tractography/mapping/buffer_scratch_dump.h +++ b/src/dwi/tractography/mapping/buffer_scratch_dump.h @@ -17,10 +17,12 @@ #include -#include -#include "file/ofstream.h" #include "image.h" +#include "types.h" + +#include "file/ofstream.h" + namespace MR { diff --git a/src/dwi/tractography/mapping/mapper.h b/src/dwi/tractography/mapping/mapper.h index eda3a720fe..e2eb91fc5c 100644 --- a/src/dwi/tractography/mapping/mapper.h +++ b/src/dwi/tractography/mapping/mapper.h @@ -16,12 +16,10 @@ #define __dwi_tractography_mapping_mapper_h__ - -#include - #include "image.h" -#include "transform.h" #include "thread_queue.h" +#include "transform.h" +#include "types.h" #include "dwi/directions/set.h" diff --git a/src/dwi/tractography/mapping/mapper_plugins.h b/src/dwi/tractography/mapping/mapper_plugins.h index ad268d73e1..0a9dcf9e6a 100644 --- a/src/dwi/tractography/mapping/mapper_plugins.h +++ b/src/dwi/tractography/mapping/mapper_plugins.h @@ -16,9 +16,8 @@ #define __dwi_tractography_mapping_mapper_plugins_h__ -#include - #include "image.h" +#include "types.h" #include "interp/linear.h" #include "math/SH.h" diff --git a/src/dwi/tractography/mapping/mapping.h b/src/dwi/tractography/mapping/mapping.h index b713d177a7..a0d386e184 100644 --- a/src/dwi/tractography/mapping/mapping.h +++ b/src/dwi/tractography/mapping/mapping.h @@ -15,10 +15,10 @@ #ifndef __dwi_tractography_mapping_mapping_h__ #define __dwi_tractography_mapping_mapping_h__ -#include - #include "header.h" #include "progressbar.h" +#include "types.h" + #include "dwi/tractography/properties.h" #include "dwi/tractography/file.h" diff --git a/src/dwi/tractography/resampling/downsampler.h b/src/dwi/tractography/resampling/downsampler.h index 62a753008b..64af49174b 100644 --- a/src/dwi/tractography/resampling/downsampler.h +++ b/src/dwi/tractography/resampling/downsampler.h @@ -16,8 +16,6 @@ #define __dwi_tractography_resampling_downsampler_h__ -#include - #include "dwi/tractography/tracking/generated_track.h" #include "dwi/tractography/resampling/resampling.h" diff --git a/src/dwi/tractography/resampling/resampling.cpp b/src/dwi/tractography/resampling/resampling.cpp index 647489fd88..9a47839fea 100644 --- a/src/dwi/tractography/resampling/resampling.cpp +++ b/src/dwi/tractography/resampling/resampling.cpp @@ -14,6 +14,8 @@ #include "dwi/tractography/resampling/resampling.h" +#include "types.h" + #include "dwi/tractography/resampling/arc.h" #include "dwi/tractography/resampling/downsampler.h" #include "dwi/tractography/resampling/endpoints.h" diff --git a/src/dwi/tractography/resampling/resampling.h b/src/dwi/tractography/resampling/resampling.h index 6bdfe79dbc..3d82e4a840 100644 --- a/src/dwi/tractography/resampling/resampling.h +++ b/src/dwi/tractography/resampling/resampling.h @@ -16,8 +16,6 @@ #define __dwi_tractography_resampling_resampling_h__ -#include - #include "app.h" #include "dwi/tractography/streamline.h" diff --git a/src/dwi/tractography/resampling/upsampler.h b/src/dwi/tractography/resampling/upsampler.h index c0d27c9b50..c7734ec159 100644 --- a/src/dwi/tractography/resampling/upsampler.h +++ b/src/dwi/tractography/resampling/upsampler.h @@ -16,8 +16,6 @@ #define __dwi_tractography_resampling_upsampler_h__ -#include - #include "dwi/tractography/resampling/resampling.h" diff --git a/src/dwi/tractography/seeding/list.h b/src/dwi/tractography/seeding/list.h index 8850369870..f5de9b7c1d 100644 --- a/src/dwi/tractography/seeding/list.h +++ b/src/dwi/tractography/seeding/list.h @@ -16,10 +16,9 @@ #define __dwi_tractography_seeding_list_h__ -#include "dwi/tractography/seeding/base.h" - -#include +#include "types.h" +#include "dwi/tractography/seeding/base.h" namespace MR diff --git a/src/dwi/tractography/streamline.h b/src/dwi/tractography/streamline.h index e0a3951c21..6b0d2c5602 100644 --- a/src/dwi/tractography/streamline.h +++ b/src/dwi/tractography/streamline.h @@ -16,8 +16,6 @@ #define __dwi_tractography_streamline_h__ -#include - #include "types.h" diff --git a/src/dwi/tractography/tracking/generated_track.h b/src/dwi/tractography/tracking/generated_track.h index c0c0172250..46a9ae3c93 100644 --- a/src/dwi/tractography/tracking/generated_track.h +++ b/src/dwi/tractography/tracking/generated_track.h @@ -16,7 +16,7 @@ #define __dwi_tractography_tracking_generated_track_h__ -#include +#include "types.h" #include "dwi/tractography/tracking/types.h" diff --git a/src/dwi/tractography/tracking/shared.h b/src/dwi/tractography/tracking/shared.h index d14a33ec1f..de6b8c2fc0 100644 --- a/src/dwi/tractography/tracking/shared.h +++ b/src/dwi/tractography/tracking/shared.h @@ -16,7 +16,6 @@ #define __dwi_tractography_tracking_shared_h__ #include -#include #include "header.h" #include "image.h" diff --git a/src/dwi/tractography/tracking/write_kernel.h b/src/dwi/tractography/tracking/write_kernel.h index 329725f5c8..f693ccb00f 100644 --- a/src/dwi/tractography/tracking/write_kernel.h +++ b/src/dwi/tractography/tracking/write_kernel.h @@ -15,9 +15,8 @@ #ifndef __dwi_tractography_tracking_write_kernel_h__ #define __dwi_tractography_tracking_write_kernel_h__ -#include -#include #include +#include #include "timer.h" #include "file/ofstream.h" diff --git a/src/gui/mrview/tool/connectome/connectome.h b/src/gui/mrview/tool/connectome/connectome.h index 634c150595..957f5ecd3e 100644 --- a/src/gui/mrview/tool/connectome/connectome.h +++ b/src/gui/mrview/tool/connectome/connectome.h @@ -16,10 +16,10 @@ #define __gui_mrview_tool_connectome_connectome_h__ #include -#include #include "bitset.h" #include "image.h" +#include "types.h" #include "gui/opengl/gl.h" #include "gui/opengl/lighting.h" diff --git a/src/gui/mrview/tool/connectome/node.cpp b/src/gui/mrview/tool/connectome/node.cpp index 91703c32f8..2d3f097e5e 100644 --- a/src/gui/mrview/tool/connectome/node.cpp +++ b/src/gui/mrview/tool/connectome/node.cpp @@ -15,9 +15,8 @@ #include "gui/mrview/window.h" #include "gui/mrview/tool/connectome/node.h" -#include - #include "exception.h" +#include "types.h" #include "gui/mrview/window.h" namespace MR diff --git a/src/gui/mrview/tool/connectome/node_overlay.cpp b/src/gui/mrview/tool/connectome/node_overlay.cpp index 7f4f362a98..d2c6935dcf 100644 --- a/src/gui/mrview/tool/connectome/node_overlay.cpp +++ b/src/gui/mrview/tool/connectome/node_overlay.cpp @@ -15,7 +15,8 @@ #include "gui/mrview/tool/connectome/node_overlay.h" #include -#include + +#include "types.h" namespace MR { diff --git a/src/gui/mrview/tool/odf/model.h b/src/gui/mrview/tool/odf/model.h index 39e812ea8d..8bd721bb03 100644 --- a/src/gui/mrview/tool/odf/model.h +++ b/src/gui/mrview/tool/odf/model.h @@ -17,7 +17,8 @@ #include #include -#include + +#include "types.h" #include "gui/mrview/tool/odf/item.h" #include "gui/mrview/tool/odf/type.h" diff --git a/src/gui/mrview/tool/roi_editor/item.cpp b/src/gui/mrview/tool/roi_editor/item.cpp index 76ae50a7c0..df60e20417 100644 --- a/src/gui/mrview/tool/roi_editor/item.cpp +++ b/src/gui/mrview/tool/roi_editor/item.cpp @@ -14,7 +14,6 @@ #include #include -#include #include "gui/mrview/tool/roi_editor/item.h" diff --git a/src/gui/mrview/tool/roi_editor/item.h b/src/gui/mrview/tool/roi_editor/item.h index 3c1fe25824..bd6e7914c7 100644 --- a/src/gui/mrview/tool/roi_editor/item.h +++ b/src/gui/mrview/tool/roi_editor/item.h @@ -17,9 +17,9 @@ #include -#include #include "header.h" +#include "types.h" #include "algo/loop.h" #include "gui/mrview/volume.h" #include "gui/mrview/tool/roi_editor/undoentry.h" diff --git a/src/gui/mrview/tool/roi_editor/roi.h b/src/gui/mrview/tool/roi_editor/roi.h index 7d833096c7..aaf386c175 100644 --- a/src/gui/mrview/tool/roi_editor/roi.h +++ b/src/gui/mrview/tool/roi_editor/roi.h @@ -15,10 +15,9 @@ #ifndef __gui_mrview_tool_roi_editor_roi_h__ #define __gui_mrview_tool_roi_editor_roi_h__ -#include - #include "memory.h" #include "transform.h" +#include "types.h" #include "gui/mrview/mode/base.h" #include "gui/mrview/tool/base.h" diff --git a/src/gui/mrview/tool/roi_editor/undoentry.cpp b/src/gui/mrview/tool/roi_editor/undoentry.cpp index 95dbf21f4e..8a7f55b6e6 100644 --- a/src/gui/mrview/tool/roi_editor/undoentry.cpp +++ b/src/gui/mrview/tool/roi_editor/undoentry.cpp @@ -13,7 +13,6 @@ #include -#include #include "gui/mrview/window.h" #include "gui/mrview/tool/roi_editor/item.h" diff --git a/src/gui/mrview/tool/roi_editor/undoentry.h b/src/gui/mrview/tool/roi_editor/undoentry.h index 49fed88e4c..99cd3a351e 100644 --- a/src/gui/mrview/tool/roi_editor/undoentry.h +++ b/src/gui/mrview/tool/roi_editor/undoentry.h @@ -17,7 +17,8 @@ #include #include -#include + +#include "types.h" #include "gui/opengl/shader.h" #include "gui/opengl/gl.h" diff --git a/src/gui/shapes/cylinder.cpp b/src/gui/shapes/cylinder.cpp index 7aa321485b..c83f65507f 100644 --- a/src/gui/shapes/cylinder.cpp +++ b/src/gui/shapes/cylinder.cpp @@ -14,7 +14,7 @@ #include "gui/shapes/cylinder.h" -#include +#include "types.h" #include "math/math.h" diff --git a/src/gui/shapes/cylinder.h b/src/gui/shapes/cylinder.h index 16c29f9fd6..e75915e587 100644 --- a/src/gui/shapes/cylinder.h +++ b/src/gui/shapes/cylinder.h @@ -15,8 +15,6 @@ #ifndef __gui_shapes_cylinder_h__ #define __gui_shapes_cylinder_h__ -#include - #include "gui/opengl/gl.h" #include "gui/opengl/gl_core_3_3.h" diff --git a/src/gui/shapes/halfsphere.h b/src/gui/shapes/halfsphere.h index 737aa3565a..5c84b7d828 100644 --- a/src/gui/shapes/halfsphere.h +++ b/src/gui/shapes/halfsphere.h @@ -15,7 +15,7 @@ #ifndef __gui_shapes_halfsphere_h__ #define __gui_shapes_halfsphere_h__ -#include +#include "types.h" #include "gui/opengl/gl.h" #include "gui/opengl/gl_core_3_3.h" diff --git a/src/gui/shapes/sphere.h b/src/gui/shapes/sphere.h index f7ffe6f369..70608a642b 100644 --- a/src/gui/shapes/sphere.h +++ b/src/gui/shapes/sphere.h @@ -15,7 +15,7 @@ #ifndef __gui_shapes_sphere_h__ #define __gui_shapes_sphere_h__ -#include +#include "types.h" #include "gui/opengl/gl.h" #include "gui/opengl/gl_core_3_3.h" diff --git a/src/registration/linear.h b/src/registration/linear.h index 1f1a8357c9..f562694059 100644 --- a/src/registration/linear.h +++ b/src/registration/linear.h @@ -15,10 +15,11 @@ #ifndef __registration_linear_h__ #define __registration_linear_h__ -#include +#include #include "app.h" #include "image.h" +#include "types.h" #include "math/average_space.h" #include "filter/normalise.h" #include "filter/resize.h" @@ -36,7 +37,7 @@ // #include "math/check_gradient.h" #include "math/rng.h" #include "math/math.h" -#include + #include "registration/multi_resolution_lmax.h" namespace MR diff --git a/src/registration/nonlinear.h b/src/registration/nonlinear.h index 43444f23f0..0fb1a16e51 100644 --- a/src/registration/nonlinear.h +++ b/src/registration/nonlinear.h @@ -15,8 +15,9 @@ #ifndef __registration_nonlinear_h__ #define __registration_nonlinear_h__ -#include #include "image.h" +#include "types.h" + #include "filter/warp.h" #include "filter/resize.h" #include "registration/transform/reorient.h" diff --git a/src/registration/transform/search.h b/src/registration/transform/search.h index 52b4c1467f..4e23814013 100644 --- a/src/registration/transform/search.h +++ b/src/registration/transform/search.h @@ -15,17 +15,19 @@ #ifndef __registration_transform_search_h__ #define __registration_transform_search_h__ -#include #include #include #include +#include "debug.h" +#include "image.h" +#include "progressbar.h" +#include "types.h" + #include "math/math.h" #include "math/median.h" #include "math/rng.h" #include "math/gradient_descent.h" -#include "image.h" -#include "debug.h" #include "math/average_space.h" #include "filter/resize.h" #include "filter/reslice.h" @@ -40,7 +42,6 @@ #include "registration/metric/thread_kernel.h" #include "registration/transform/initialiser.h" #include "registration/transform/rigid.h" -#include "progressbar.h" #include "file/config.h" namespace MR diff --git a/src/stats/permstack.h b/src/stats/permstack.h index 9fa6152e22..605319b96b 100644 --- a/src/stats/permstack.h +++ b/src/stats/permstack.h @@ -17,9 +17,9 @@ #include #include -#include #include "progressbar.h" +#include "types.h" #include "math/stats/permutation.h" namespace MR diff --git a/src/surface/algo/image2mesh.h b/src/surface/algo/image2mesh.h index 3c08e1b021..bfc2df8472 100644 --- a/src/surface/algo/image2mesh.h +++ b/src/surface/algo/image2mesh.h @@ -17,10 +17,10 @@ #include #include -#include #include "image_helpers.h" #include "transform.h" +#include "types.h" #include "surface/mesh.h" #include "surface/types.h" diff --git a/src/surface/algo/mesh2image.cpp b/src/surface/algo/mesh2image.cpp index fc29bd6e60..b4280330b7 100644 --- a/src/surface/algo/mesh2image.cpp +++ b/src/surface/algo/mesh2image.cpp @@ -15,10 +15,10 @@ #include "surface/algo/mesh2image.h" #include -#include #include "header.h" #include "progressbar.h" +#include "types.h" #include "surface/types.h" #include "surface/utils.h" diff --git a/src/surface/mesh.cpp b/src/surface/mesh.cpp index 016fd89027..53bffa8e65 100644 --- a/src/surface/mesh.cpp +++ b/src/surface/mesh.cpp @@ -17,7 +17,8 @@ #include #include #include -#include + +#include "types.h" #include "surface/freesurfer.h" #include "surface/utils.h" diff --git a/src/surface/mesh.h b/src/surface/mesh.h index f0474b7ce4..aaefe03bc8 100644 --- a/src/surface/mesh.h +++ b/src/surface/mesh.h @@ -16,9 +16,8 @@ #define __surface_mesh_h__ -#include -#include #include +#include #include "header.h" #include "image.h" diff --git a/src/surface/mesh_multi.h b/src/surface/mesh_multi.h index 4b8932a033..ef4d3ba1d1 100644 --- a/src/surface/mesh_multi.h +++ b/src/surface/mesh_multi.h @@ -16,7 +16,7 @@ #define __surface_mesh_multi_h__ -#include +#include "types.h" #include "surface/mesh.h" From 6f9e063f13ae22400c38005704b018989f16662c Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Tue, 23 May 2017 10:45:25 +0100 Subject: [PATCH 013/139] replace MR::vector with just vector As discussed in #996. Also some other minor cosmetic changes. --- .../mrview/tool/tractography/tractography.cpp | 101 ++++++++++-------- .../mrview/tool/tractography/tractography.h | 4 +- 2 files changed, 59 insertions(+), 46 deletions(-) diff --git a/src/gui/mrview/tool/tractography/tractography.cpp b/src/gui/mrview/tool/tractography/tractography.cpp index 14a77b23eb..ba142daf11 100644 --- a/src/gui/mrview/tool/tractography/tractography.cpp +++ b/src/gui/mrview/tool/tractography/tractography.cpp @@ -41,7 +41,7 @@ namespace MR Model (QObject* parent) : ListModelBase (parent) { } - void add_items (MR::vector& filenames, + void add_items (vector& filenames, Tractography& tractography_tool) { for (size_t i = 0; i < filenames.size(); ++i) { @@ -288,11 +288,15 @@ namespace MR void Tractography::tractogram_open_slot () { - MR::vector list = Dialog::File::get_files (this, "Select tractograms to open", "Tractograms (*.tck)"); + vector list = Dialog::File::get_files (this, "Select tractograms to open", "Tractograms (*.tck)"); add_tractogram(list); } - void Tractography::add_tractogram (MR::vector& list) + + + + + void Tractography::add_tractogram (vector& list) { if (list.empty()) { return; } @@ -306,13 +310,17 @@ namespace MR } + + + + void Tractography::dropEvent (QDropEvent* event) { static constexpr int max_files = 32; const QMimeData* mimeData = event->mimeData(); if (mimeData->hasUrls()) { - MR::vector list; + 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()); @@ -699,84 +707,80 @@ namespace MR */ void Tractography::select_last_added_tractogram() { - int count = tractogram_list_model->rowCount(); - if(count != 0){ - QModelIndex index = tractogram_list_view->model()->index(count-1, 0); - tractogram_list_view->setCurrentIndex(index); - window().updateGL(); - } + int count = tractogram_list_model->rowCount(); + if(count != 0){ + QModelIndex index = tractogram_list_view->model()->index(count-1, 0); + tractogram_list_view->setCurrentIndex(index); + window().updateGL(); + } } - + + + + + bool Tractography::process_commandline_option (const MR::App::ParsedOption& opt) { if (opt.opt->is ("tractography.load")) { - MR::vector list (1, std::string(opt[0])); + vector list (1, std::string(opt[0])); add_tractogram(list); return true; } - if (opt.opt->is("tractography.tsf_load")) + if (opt.opt->is ("tractography.tsf_load")) { - try - { + try { - if(process_commandline_option_tsf_check_tracto_loaded()) - { + if (process_commandline_option_tsf_check_tracto_loaded()) { QModelIndexList indices = tractogram_list_view->selectionModel()->selectedIndexes(); - if(indices.size() == 1) {//just in case future edits break this assumption + if (indices.size() == 1) {//just in case future edits break this assumption Tractogram* tractogram = tractogram_list_model->get_tractogram (indices[0]); //set its tsf filename and load the tsf file - scalar_file_options->set_tractogram(tractogram); - scalar_file_options->open_intensity_track_scalar_file_slot(std::string(opt[0])); + scalar_file_options->set_tractogram (tractogram); + scalar_file_options->open_intensity_track_scalar_file_slot (std::string(opt[0])); //Set the GUI to use the file for visualisation colour_combobox->setCurrentIndex(4); // Set combobox to "File" } } } - catch(Exception& E) { E.display(); } + catch (Exception& E) { E.display(); } return true; } - if (opt.opt->is("tractography.tsf_range")) + if (opt.opt->is ("tractography.tsf_range")) { - try - { + try { //Set the tsf visualisation range - MR::vector range; - if(process_commandline_option_tsf_option(opt,2, range)) - { - scalar_file_options->set_scaling(range[0], range[1]); - } + vector range; + if (process_commandline_option_tsf_option(opt,2, range)) + scalar_file_options->set_scaling (range[0], range[1]); } - catch(Exception& E) { E.display(); } - return true; + catch (Exception& E) { E.display(); } + return true; } + - - if (opt.opt->is("tractography.tsf_thresh")) + if (opt.opt->is ("tractography.tsf_thresh")) { - try - { + try { //Set the tsf visualisation threshold - MR::vector range; - if(process_commandline_option_tsf_option(opt,2, range)) - { - scalar_file_options->set_threshold(TrackThresholdType::UseColourFile,range[0], range[1]); - } + vector range; + if (process_commandline_option_tsf_option(opt,2, range)) + scalar_file_options->set_threshold (TrackThresholdType::UseColourFile,range[0], range[1]); } catch(Exception& E) { E.display(); } return true; } - - - + + + if (opt.opt->is ("tractography.thickness")) { // Thickness runs from -1000 to 1000, float thickness = float(opt[0]) * 1000.0f; @@ -817,6 +821,10 @@ namespace MR } + + + + /*Checks whether any tractography has been loaded and warns the user if it has not*/ bool Tractography::process_commandline_option_tsf_check_tracto_loaded() { @@ -827,9 +835,14 @@ namespace MR } return count != 0; } + + + + + /*Checks whether legal to apply tsf options and prepares the scalar_file_options to do so. Returns the vector of floats parsed from the options, or null on fail*/ - bool Tractography::process_commandline_option_tsf_option(const MR::App::ParsedOption& opt, uint reqArgSize, MR::vector& range) + bool Tractography::process_commandline_option_tsf_option(const MR::App::ParsedOption& opt, uint reqArgSize, vector& range) { if(process_commandline_option_tsf_check_tracto_loaded()){ QModelIndexList indices = tractogram_list_view->selectionModel()->selectedIndexes(); diff --git a/src/gui/mrview/tool/tractography/tractography.h b/src/gui/mrview/tool/tractography/tractography.h index fa1d452466..6f35bc1c13 100644 --- a/src/gui/mrview/tool/tractography/tractography.h +++ b/src/gui/mrview/tool/tractography/tractography.h @@ -107,10 +107,10 @@ namespace MR void dropEvent (QDropEvent* event) override; void update_scalar_options(); - void add_tractogram (MR::vector& list); + void add_tractogram (vector& list); void select_last_added_tractogram(); bool process_commandline_option_tsf_check_tracto_loaded (); - bool process_commandline_option_tsf_option (const MR::App::ParsedOption&, uint, MR::vector& range); + bool process_commandline_option_tsf_option (const MR::App::ParsedOption&, uint, vector& range); }; } From dcb90d44c35a3372f07184636e248b8cc405dc3a Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Tue, 23 May 2017 15:38:31 +0100 Subject: [PATCH 014/139] check_memalign: "using namespace std;" also causes a failure See discussion on #996 --- check_memalign | 67 +++++++++++++++++++--------------------------- cmd/dwi2adc.cpp | 1 - cmd/dwi2tensor.cpp | 1 - cmd/peaks2amp.cpp | 1 - 4 files changed, 28 insertions(+), 42 deletions(-) diff --git a/check_memalign b/check_memalign index 1efdbdb119..ee0de9ac22 100755 --- a/check_memalign +++ b/check_memalign @@ -21,21 +21,28 @@ for f in $(find cmd core src -type f -name '*.h' -o -name '*.cpp' | grep -v '_mo "$f" -ef "core/signal_handler.cpp" ]] && continue -# detect classes not declared MEMALIGN or NOMEMALIGN: - res=$( \ - cat $f | \ + + + # process the file to strip comments, macros, etc: + cat $f | \ # remove C preprocessor macros: - grep -v '^#' | \ + grep -v '^#' | \ # remove C++ single-line comments: - perl -pe 's|//.*$||' | \ + perl -pe 's|//.*$||' | \ # remove all newlines to make file one long line: - tr '\n' ' ' | \ + tr '\n' ' ' | \ # remove any duplicate spaces: - perl -pe 's|\s+| |g' | \ + perl -pe 's|\s+| |g' | \ # remove C-style comments: - perl -pe 's|/\*.*?\*/||g' | \ + perl -pe 's|/\*.*?\*/||g' | \ # remove quoted strings: - perl -pe 's/(")(\\"|.)*?"//g' | \ + perl -pe 's/(")(\\"|.)*?"//g' > .check_memalign.tmp + + +# detect classes not declared MEMALIGN or NOMEMALIGN: + + res=$( + cat .check_memalign.tmp | \ # remove any text within a template declaration (i.e. within <>): perl -pe 's|<[^{};<]*?>||g' | \ # and do it multiple times to handle nested declarations: @@ -47,49 +54,31 @@ for f in $(find cmd core src -type f -name '*.h' -o -name '*.cpp' | grep -v '_mo # remove matches that correspond to an enum class declaration: grep -Ev '\benum\s*class\b' | \ # remove matches that are properly specified: - grep -Ev '\b(class|struct)\b[^;{]*?{(\s*(MEMALIGN\s*\([^\)]*\)|NOMEMALIGN))' + grep -Ev '\b(class|struct)\b[^;{]*?{(\s*(MEMALIGN\s*\([^\)]*\)|NOMEMALIGN))' ) # detect any instances of std::vector: - res="$res"$( \ - cat $f | \ -# remove C preprocessor macros: - grep -v '^#' | \ -# remove C++ single-line comments: - perl -pe 's|//.*$||' | \ -# remove all newlines to make file one long line: - tr '\n' ' ' | \ -# remove any duplicate spaces: - perl -pe 's|\s+| |g' | \ -# remove C-style comments: - perl -pe 's|/\*.*?\*/||g' | \ -# remove quoted strings: - perl -pe 's/(")(\\"|.)*?"//g' | \ + res="$res"$( + cat .check_memalign.tmp | \ # match for the parts we're interested in and output just the bits that match: grep -Po '(?> $LOG diff --git a/cmd/dwi2adc.cpp b/cmd/dwi2adc.cpp index 345631d78f..36cdd703c1 100644 --- a/cmd/dwi2adc.cpp +++ b/cmd/dwi2adc.cpp @@ -22,7 +22,6 @@ using namespace MR; using namespace App; -using namespace std; void usage () diff --git a/cmd/dwi2tensor.cpp b/cmd/dwi2tensor.cpp index a7c240d402..37a85720ea 100644 --- a/cmd/dwi2tensor.cpp +++ b/cmd/dwi2tensor.cpp @@ -21,7 +21,6 @@ using namespace MR; using namespace App; -using namespace std; using value_type = float; diff --git a/cmd/peaks2amp.cpp b/cmd/peaks2amp.cpp index 5e1177cb4a..163d7100bc 100644 --- a/cmd/peaks2amp.cpp +++ b/cmd/peaks2amp.cpp @@ -17,7 +17,6 @@ #include "algo/loop.h" -using namespace std; using namespace MR; using namespace App; From 2c51ce1cece44c948e804f6b0401ba62be8e290a Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Fri, 16 Jun 2017 15:37:31 +0100 Subject: [PATCH 015/139] tensor2metric: add option to sort eigenvalues by absolute value This is useful in non-DTI contexts (e.g. for structure tensor analysis). --- cmd/tensor2metric.cpp | 60 +++++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/cmd/tensor2metric.cpp b/cmd/tensor2metric.cpp index 3dbad85d80..dd97b85eb3 100644 --- a/cmd/tensor2metric.cpp +++ b/cmd/tensor2metric.cpp @@ -80,6 +80,10 @@ void usage () "principal (1) and minor (3) eigenvalues/eigenvectors (default = 1).") + Argument ("sequence").type_sequence_int() + + Option ("abs", + "sort eigenvalues in order of decreasing absolute value, rather than actual " + "value (the default). This interacts with the -num option above.") + + Option ("modulate", "specify how to modulate the magnitude of the eigenvectors. Valid choices " "are: none, FA, eigval (default = FA).") @@ -115,7 +119,8 @@ class Processor { MEMALIGN(Processor) Image& value_img, Image& vector_img, vector& vals, - int modulate) : + int modulate, + bool sort_by_abs) : mask_img (mask_img), adc_img (adc_img), fa_img (fa_img), @@ -127,7 +132,8 @@ class Processor { MEMALIGN(Processor) value_img (value_img), vector_img (vector_img), vals (vals), - modulate (modulate) { } + modulate (modulate), + sort_by_abs (sort_by_abs) { } void operator() (Image& dt_img) { @@ -159,7 +165,7 @@ class Processor { MEMALIGN(Processor) fa_img.value() = fa; } - bool need_eigenvalues = value_img.valid() || (vector_img.valid() && (modulate == 2)) || ad_img.valid() || rd_img.valid() || cl_img.valid() || cp_img.valid() || cs_img.valid(); + bool need_eigenvalues = value_img.valid() || (vector_img.valid() && (modulate == 2 || sort_by_abs)) || ad_img.valid() || rd_img.valid() || cl_img.valid() || cp_img.valid() || cs_img.valid(); Eigen::SelfAdjointEigenSolver es; if (need_eigenvalues || vector_img.valid()) { @@ -174,8 +180,18 @@ class Processor { MEMALIGN(Processor) } Eigen::Vector3d eigval; - if (need_eigenvalues) + ssize_t ith_eig[3] = { 2, 1, 0 }; + if (need_eigenvalues) { eigval = es.eigenvalues(); + if (sort_by_abs) { + ith_eig[0] = 0; ith_eig[1] = 1; ith_eig[2] = 2; + std::sort (std::begin (ith_eig), std::end (ith_eig), + [&eigval](size_t a, size_t b) { return std::abs(eigval[a]) > std::abs(eigval[b]); }); + } + //std::cerr << ith_eig[0] << ": " << eigval[ith_eig[0]] << "; "; + //std::cerr << ith_eig[1] << ": " << eigval[ith_eig[1]] << "; "; + //std::cerr << ith_eig[2] << ": " << eigval[ith_eig[2]] << "\n"; + } /* output value */ if (value_img.valid()) { @@ -183,10 +199,10 @@ class Processor { MEMALIGN(Processor) if (vals.size() > 1) { auto l = Loop(3)(value_img); for (size_t i = 0; i < vals.size(); i++) { - value_img.value() = eigval(3-vals[i]); l++; + value_img.value() = eigval(ith_eig[vals[i]]); l++; } } else { - value_img.value() = eigval(3-vals[0]); + value_img.value() = eigval(ith_eig[vals[0]]); } } @@ -231,10 +247,10 @@ class Processor { MEMALIGN(Processor) if (modulate == 1) fact = fa; else if (modulate == 2) - fact = eigval(3-vals[i]); - vector_img.value() = eigvec(0,3-vals[i])*fact; l++; - vector_img.value() = eigvec(1,3-vals[i])*fact; l++; - vector_img.value() = eigvec(2,3-vals[i])*fact; l++; + fact = eigval(ith_eig[vals[i]]); + vector_img.value() = eigvec(0,ith_eig[vals[i]])*fact; l++; + vector_img.value() = eigvec(1,ith_eig[vals[i]])*fact; l++; + vector_img.value() = eigvec(2,ith_eig[vals[i]])*fact; l++; } } } @@ -252,8 +268,16 @@ class Processor { MEMALIGN(Processor) Image vector_img; vector vals; int modulate; + bool sort_by_abs; }; + + + + + + + void run () { auto dt_img = Image::open (argument[0]); @@ -319,13 +343,17 @@ void run () opt = get_options ("num"); if (opt.size()) { vals = opt[0][0]; - if (vals.empty()) - throw Exception ("invalid eigenvalue/eigenvector number specifier"); - for (size_t i = 0; i < vals.size(); ++i) - if (vals[i] < 1 || vals[i] > 3) - throw Exception ("eigenvalue/eigenvector number is out of bounds"); + if (vals.empty()) + throw Exception ("invalid eigenvalue/eigenvector number specifier"); + for (size_t i = 0; i < vals.size(); ++i) { + if (vals[i] < 1 || vals[i] > 3) + throw Exception ("eigenvalue/eigenvector number is out of bounds"); + --vals[i]; + } } + bool sort_by_abs = get_options ("abs").size(); + float modulate = get_option_value ("modulate", 1); auto value_img = Image(); @@ -348,5 +376,5 @@ void run () } ThreadedLoop ("computing metrics", dt_img, 0, 3) - .run (Processor (mask_img, adc_img, fa_img, ad_img, rd_img, cl_img, cp_img, cs_img, value_img, vector_img, vals, modulate), dt_img); + .run (Processor (mask_img, adc_img, fa_img, ad_img, rd_img, cl_img, cp_img, cs_img, value_img, vector_img, vals, modulate, sort_by_abs), dt_img); } From ad05b488d8bee1e7de5f573ca24f312283e67929 Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Fri, 23 Jun 2017 12:13:00 +1000 Subject: [PATCH 016/139] mt_lognorm: Adding new command for intensity normalisation of multi-tissue images with additional bias field correction --- cmd/mt_lognorm.cpp | 412 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 cmd/mt_lognorm.cpp diff --git a/cmd/mt_lognorm.cpp b/cmd/mt_lognorm.cpp new file mode 100644 index 0000000000..8ff6ff00b3 --- /dev/null +++ b/cmd/mt_lognorm.cpp @@ -0,0 +1,412 @@ +/* Copyright (c) 2008-2017 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 http://www.mrtrix.org/. + */ + + +#include "command.h" +#include "image.h" +#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" +#include "algo/threaded_copy.h" + +using namespace MR; +using namespace App; + +#define DEFAULT_NORM_VALUE 0.282094 +#define DEFAULT_MAXITER_VALUE 10 + +void usage () +{ + AUTHOR = "David Raffelt (david.raffelt@florey.edu.au), Rami Tabbara (rami.tabbara@florey.edu.au) and Thijs Dhollander (thijs.dhollander@gmail.com)"; + + SYNOPSIS = "Multi-Tissue Bias field correction and Intensity Normalisation (MTBIN)"; + + DESCRIPTION + + "This command inputs N number of tissue components " + "(e.g. from multi-tissue CSD), and outputs N corrected tissue components. Intensity normalisation is performed by either " + "determining a common global normalisation factor for all tissue types (default) or by normalising each tissue type independently " + "with a single tissue-specific global scale factor." + + + "The -mask option is mandatory, and is optimally provided with a brain mask, such as the one obtained from dwi2mask earlier in the processing pipeline." + + + "Example usage: mtbin wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.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. " + "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. This option is mandatory.").required () + + Argument ("image").type_image_in () + + + Option ("value", "specify the value to which the summed tissue compartments will be normalised to " + "(Default: sqrt(1/(4*pi)) = " + str(DEFAULT_NORM_VALUE, 6) + ")") + + Argument ("number").type_float () + + + 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() + + + Option ("check", "check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure. However, these regions are still corrected for bias fields based on the other image data.") + + Argument ("image").type_image_out (); +} + +const int n_basis_vecs (20); + + +FORCE_INLINE Eigen::MatrixXd basis_function (const Eigen::Vector3 pos) { + 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; + 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 * z; + 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; + basis(19) = x * y * z; + return basis; +} + +// Currently not used, but keep if we want to make mask argument optional in the future +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); +} + + +FORCE_INLINE void refine_mask (Image& summed, + Image& initial_mask, + Image& refined_mask) { + + for (auto i = Loop (summed, 0, 3) (summed, initial_mask, refined_mask); i; ++i) { + if (std::isfinite((float) summed.value ()) && summed.value () > 0.f && initial_mask.value ()) + refined_mask.value () = true; + else + refined_mask.value () = false; + } +} + + +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"); + + ProgressBar progress ("performing intensity normalisation and bias field correction..."); + vector > input_images; + vector
output_headers; + vector output_filenames; + + + // Open input images and check for output + for (size_t i = 0; i < argument.size(); i += 2) { + progress++; + 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); + + 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])); + output_filenames.push_back (argument[i + 1]); + } + + const size_t n_tissue_types = input_images.size(); + + // Load the mask + Header header_3D (input_images[0]); + header_3D.ndim() = 3; + auto opt = get_options ("mask"); + + auto orig_mask = Image::open (opt[0][0]); + auto initial_mask = Image::scratch (orig_mask); + auto mask = Image::scratch (orig_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]); i; ++i) + summed.value() += input_images[j].value(); + progress++; + } + + // Refine the initial mask to exclude non-positive summed tissue components + refine_mask (summed, orig_mask, initial_mask); + + threaded_copy (initial_mask, mask); + + size_t num_voxels = 0; + for (auto i = Loop (mask) (mask); i; ++i) { + if (mask.value()) + num_voxels++; + } + 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); + + if (normalisation_value <= 0.f) + throw Exception ("Intensity normalisation value must be strictly positive."); + + const float log_norm_value = std::log (normalisation_value); + const size_t max_iter = get_option_value ("maxiter", DEFAULT_MAXITER_VALUE); + + // Initialise bias field weights + Eigen::MatrixXd bias_field_weights (n_basis_vecs, 0); + + // Initialise bias fields in both image and log domain + auto bias_field_image = Image::scratch (header_3D); + auto bias_field_log = Image::scratch (header_3D); + + for (auto i = Loop(bias_field_log) (bias_field_image, bias_field_log); i; ++i) { + bias_field_image.value() = 1.f; + bias_field_log.value() = 0.f; + } + + Eigen::VectorXd scale_factors (n_tissue_types); + Eigen::VectorXd previous_scale_factors (n_tissue_types); + + size_t iter = 1; + + // Iterate until convergence or max iterations performed + while (iter < max_iter) { + + + INFO ("iteration: " + str(iter)); + + // Iteratively compute intensity normalisation scale factors + // with outlier rejection + size_t norm_iter = 1; + bool norm_converged = false; + + while (!norm_converged && norm_iter < max_iter) { + + INFO ("norm iteration: " + str(iter)); + + // Solve for tissue normalisation scale factors + Eigen::MatrixXd X (num_voxels, input_images.size()); + Eigen::VectorXd y (num_voxels); + y.fill (1); + uint32_t index = 0; + for (auto i = Loop (mask) (mask, bias_field_image); i; ++i) { + if (mask.value()) { + for (size_t j = 0; j < n_tissue_types; ++j) { + assign_pos_of (mask, 0, 3).to (input_images[j]); + X (index, j) = input_images[j].value() / bias_field_image.value(); + } + ++index; + } + } + + scale_factors = X.colPivHouseholderQr().solve(y); + + INFO ("scale factors in iteration: " + str(scale_factors.transpose())); + + // Ensure our scale factors satisfy the condition that sum(log(scale_factors)) = 0 + double log_sum = 0.f; + for (size_t j = 0; j < n_tissue_types; ++j) + log_sum += std::log (scale_factors(j)); + scale_factors /= std::exp (log_sum / n_tissue_types); + + INFO ("log-normalised scale factors in iteration: " + str(scale_factors.transpose())); + + // Check for convergence + Eigen::MatrixXd diff; + if (iter > 1) { + diff = previous_scale_factors.array() - scale_factors.array(); + diff = diff.array().abs() / previous_scale_factors.array(); + INFO ("percentage change in estimated scale factors: " + str(diff.mean() * 100)); + if (diff.mean() < 0.001) + norm_converged = true; + } + + // Perform outlier rejection on log-domain of summed images + if (!norm_converged) { + + INFO ("Performing outlier rejection"); + + auto summed_log = Image::scratch (header_3D); + for (size_t j = 0; j < input_images.size(); ++j) { + for (auto i = Loop (summed_log, 0, 3) (summed_log, input_images[j], bias_field_image); i; ++i) { + summed_log.value() += scale_factors(j) * input_images[j].value() / bias_field_image.value(); + } + + summed_log.value() = std::log(summed_log.value()); + } + + INFO ("Loaded log sum image"); + + refine_mask (summed_log, initial_mask, mask); + + vector summed_log_values; + for (auto i = Loop (mask) (mask, summed_log); i; ++i) { + if (mask.value()) + summed_log_values.push_back (summed_log.value()); + } + + num_voxels = summed_log_values.size(); + + INFO ("Flatten log sum image: Number of voxels " + str(num_voxels)); + + std::sort (summed_log_values.begin(), summed_log_values.end()); + float lower_quartile = summed_log_values[std::round ((float)num_voxels * 0.25)]; + float upper_quartile = summed_log_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); + + INFO ("Finding quartile ranges"); + + for (auto i = Loop (mask) (mask, summed_log); i; ++i) { + if (mask.value()) { + if (summed_log.value() < lower_outlier_threshold || summed_log.value() > upper_outlier_threshold) { + mask.value() = 0; + num_voxels--; + } + } + } + + if (log_level >= 3) + display (mask); + } + + previous_scale_factors = scale_factors; + + norm_iter++; + } + + + INFO ("scale factors: " + str(scale_factors.transpose())); + + + // Solve for bias field weights in the log domain + Transform transform (mask); + Eigen::MatrixXd bias_field_basis (num_voxels, n_basis_vecs); + Eigen::MatrixXd X (num_voxels, input_images.size()); + Eigen::VectorXd y (num_voxels); + uint32_t 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) * input_images[j].value() ; + } + y (index++) = std::log(sum) - log_norm_value; + } + } + + bias_field_weights = bias_field_basis.colPivHouseholderQr().solve(y); + + // Generate bias field in the log domain + for (auto i = Loop (bias_field_log) (bias_field_log, mask); i; ++i) { + Eigen::Vector3 vox (bias_field_log.index(0), bias_field_log.index(1), bias_field_log.index(2)); + Eigen::Vector3 pos = transform.voxel2scanner * vox; + bias_field_log.value() = basis_function (pos).col(0).dot (bias_field_weights.col(0)); + } + + // Generate bias field in the image domain + for (auto i = Loop (bias_field_log) (bias_field_log, bias_field_image, mask); i; ++i) + bias_field_image.value () = std::exp(bias_field_log.value()); + + progress++; + iter++; + } + + opt = get_options ("bias"); + if (opt.size()) { + auto bias_field_output = Image::create (opt[0][0], header_3D); + threaded_copy (bias_field_image, 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"); + 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 + 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_image); + output_image.value() = scale_factors(j, 0) * input_images[j].value() / bias_field_image.value(); + } + } +} From c746f4887cbf8baa7b116e2107a709e5bda645aa Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Fri, 23 Jun 2017 12:17:29 +1000 Subject: [PATCH 017/139] mt_lognorm -> mtlognorm --- cmd/{mt_lognorm.cpp => mtlognorm.cpp} | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) rename cmd/{mt_lognorm.cpp => mtlognorm.cpp} (96%) diff --git a/cmd/mt_lognorm.cpp b/cmd/mtlognorm.cpp similarity index 96% rename from cmd/mt_lognorm.cpp rename to cmd/mtlognorm.cpp index 8ff6ff00b3..a2e0a08ad6 100644 --- a/cmd/mt_lognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -33,7 +33,7 @@ void usage () { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au), Rami Tabbara (rami.tabbara@florey.edu.au) and Thijs Dhollander (thijs.dhollander@gmail.com)"; - SYNOPSIS = "Multi-Tissue Bias field correction and Intensity Normalisation (MTBIN)"; + SYNOPSIS = "Multi-Tissue Intensity Normalisation and Bias-field correction."; DESCRIPTION + "This command inputs N number of tissue components " @@ -252,16 +252,12 @@ void run () scale_factors = X.colPivHouseholderQr().solve(y); - INFO ("scale factors in iteration: " + str(scale_factors.transpose())); - // Ensure our scale factors satisfy the condition that sum(log(scale_factors)) = 0 double log_sum = 0.f; for (size_t j = 0; j < n_tissue_types; ++j) log_sum += std::log (scale_factors(j)); scale_factors /= std::exp (log_sum / n_tissue_types); - INFO ("log-normalised scale factors in iteration: " + str(scale_factors.transpose())); - // Check for convergence Eigen::MatrixXd diff; if (iter > 1) { @@ -275,8 +271,6 @@ void run () // Perform outlier rejection on log-domain of summed images if (!norm_converged) { - INFO ("Performing outlier rejection"); - auto summed_log = Image::scratch (header_3D); for (size_t j = 0; j < input_images.size(); ++j) { for (auto i = Loop (summed_log, 0, 3) (summed_log, input_images[j], bias_field_image); i; ++i) { @@ -286,8 +280,6 @@ void run () summed_log.value() = std::log(summed_log.value()); } - INFO ("Loaded log sum image"); - refine_mask (summed_log, initial_mask, mask); vector summed_log_values; @@ -298,15 +290,12 @@ void run () num_voxels = summed_log_values.size(); - INFO ("Flatten log sum image: Number of voxels " + str(num_voxels)); - std::sort (summed_log_values.begin(), summed_log_values.end()); float lower_quartile = summed_log_values[std::round ((float)num_voxels * 0.25)]; float upper_quartile = summed_log_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); - INFO ("Finding quartile ranges"); for (auto i = Loop (mask) (mask, summed_log); i; ++i) { if (mask.value()) { From 424682243af73505495d7fcd63f6a185ba576261 Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Fri, 23 Jun 2017 15:03:10 +1000 Subject: [PATCH 018/139] mtlognorm: Zero-clamping tissue-image inputs and outputs * Also minor clean-up --- cmd/mtlognorm.cpp | 104 ++++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index a2e0a08ad6..00f6976725 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -103,21 +103,6 @@ FORCE_INLINE Eigen::MatrixXd basis_function (const Eigen::Vector3 pos) { return basis; } -// Currently not used, but keep if we want to make mask argument optional in the future -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); -} - - FORCE_INLINE void refine_mask (Image& summed, Image& initial_mask, Image& refined_mask) { @@ -145,26 +130,25 @@ void run () vector output_filenames; - // Open input images and check for output + // Open input images and prepare output image headers for (size_t i = 0; i < argument.size(); i += 2) { progress++; input_images.emplace_back (Image::open (argument[i])); - // check if all inputs have the same dimensions - if (i) + if (i > 0) check_dimensions (input_images[0], input_images[i / 2], 0, 3); 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])); - output_filenames.push_back (argument[i + 1]); + output_filenames.emplace_back (argument[i + 1]); } const size_t n_tissue_types = input_images.size(); - // Load the mask + + // Load the mask and refine the initial mask to exclude non-positive summed tissue components Header header_3D (input_images[0]); header_3D.ndim() = 3; auto opt = get_options ("mask"); @@ -180,33 +164,48 @@ void run () progress++; } - // Refine the initial mask to exclude non-positive summed tissue components refine_mask (summed, orig_mask, initial_mask); threaded_copy (initial_mask, mask); + + // Load input images into single 4d-image and zero-clamp combined-tissue image + Header h_combined_tissue (input_images[0]); + h_combined_tissue.ndim () = 4; + h_combined_tissue.size (3) = n_tissue_types; + auto combined_tissue = Image::scratch (h_combined_tissue, "Packed tissue components"); + + for (size_t i = 0; i < n_tissue_types; ++i) { + combined_tissue.index (3) = i; + for (auto l = Loop (0, 3) (combined_tissue, input_images[i]); l; ++l) { + combined_tissue.value () = std::max(input_images[i].value (), 0.f); + } + } + size_t num_voxels = 0; for (auto i = Loop (mask) (mask); i; ++i) { if (mask.value()) num_voxels++; } - progress++; if (!num_voxels) - throw Exception ("error in automatic mask generation. Mask contains no voxels"); + throw Exception ("Error in automatic mask generation. Mask contains no voxels"); + + // Load global normalisation factor const float normalisation_value = get_option_value ("value", DEFAULT_NORM_VALUE); if (normalisation_value <= 0.f) throw Exception ("Intensity normalisation value must be strictly positive."); const float log_norm_value = std::log (normalisation_value); + const size_t max_iter = get_option_value ("maxiter", DEFAULT_MAXITER_VALUE); - // Initialise bias field weights - Eigen::MatrixXd bias_field_weights (n_basis_vecs, 0); // Initialise bias fields in both image and log domain + Eigen::MatrixXd bias_field_weights (n_basis_vecs, 0); + auto bias_field_image = Image::scratch (header_3D); auto bias_field_log = Image::scratch (header_3D); @@ -220,9 +219,8 @@ void run () size_t iter = 1; - // Iterate until convergence or max iterations performed - while (iter < max_iter) { + while (iter < max_iter) { INFO ("iteration: " + str(iter)); @@ -233,18 +231,19 @@ void run () while (!norm_converged && norm_iter < max_iter) { - INFO ("norm iteration: " + str(iter)); + INFO ("norm iteration: " + str(norm_iter)); // Solve for tissue normalisation scale factors - Eigen::MatrixXd X (num_voxels, input_images.size()); + Eigen::MatrixXd X (num_voxels, n_tissue_types); Eigen::VectorXd y (num_voxels); y.fill (1); uint32_t index = 0; - for (auto i = Loop (mask) (mask, bias_field_image); i; ++i) { + + for (auto i = Loop (mask) (mask, combined_tissue, bias_field_image); i; ++i) { if (mask.value()) { for (size_t j = 0; j < n_tissue_types; ++j) { - assign_pos_of (mask, 0, 3).to (input_images[j]); - X (index, j) = input_images[j].value() / bias_field_image.value(); + combined_tissue.index (3) = j; + X (index, j) = combined_tissue.value() / bias_field_image.value(); } ++index; } @@ -254,14 +253,18 @@ void run () // Ensure our scale factors satisfy the condition that sum(log(scale_factors)) = 0 double log_sum = 0.f; - for (size_t j = 0; j < n_tissue_types; ++j) + for (size_t j = 0; j < n_tissue_types; ++j) { + if (scale_factors(j) <= 0.0) + throw Exception ("Non-positive tissue intensity normalisation scale factor was computed." + " Tissue index: " + str(j) + " Scale factor: " + str(scale_factors(j)) + + " Needs to be strictly positive!"); log_sum += std::log (scale_factors(j)); + } scale_factors /= std::exp (log_sum / n_tissue_types); // Check for convergence - Eigen::MatrixXd diff; if (iter > 1) { - diff = previous_scale_factors.array() - scale_factors.array(); + Eigen::VectorXd diff = previous_scale_factors.array() - scale_factors.array(); diff = diff.array().abs() / previous_scale_factors.array(); INFO ("percentage change in estimated scale factors: " + str(diff.mean() * 100)); if (diff.mean() < 0.001) @@ -272,9 +275,10 @@ void run () if (!norm_converged) { auto summed_log = Image::scratch (header_3D); - for (size_t j = 0; j < input_images.size(); ++j) { - for (auto i = Loop (summed_log, 0, 3) (summed_log, input_images[j], bias_field_image); i; ++i) { - summed_log.value() += scale_factors(j) * input_images[j].value() / bias_field_image.value(); + for (size_t j = 0; j < n_tissue_types; ++j) { + for (auto i = Loop (summed_log, 0, 3) (summed_log, combined_tissue, bias_field_image); i; ++i) { + combined_tissue.index(3) = j; + summed_log.value() += scale_factors(j) * combined_tissue.value() / bias_field_image.value(); } summed_log.value() = std::log(summed_log.value()); @@ -285,7 +289,7 @@ void run () vector summed_log_values; for (auto i = Loop (mask) (mask, summed_log); i; ++i) { if (mask.value()) - summed_log_values.push_back (summed_log.value()); + summed_log_values.emplace_back (summed_log.value()); } num_voxels = summed_log_values.size(); @@ -322,19 +326,19 @@ void run () // Solve for bias field weights in the log domain Transform transform (mask); Eigen::MatrixXd bias_field_basis (num_voxels, n_basis_vecs); - Eigen::MatrixXd X (num_voxels, input_images.size()); + Eigen::MatrixXd X (num_voxels, n_tissue_types); Eigen::VectorXd y (num_voxels); uint32_t index = 0; - for (auto i = Loop (mask) (mask); i; ++i) { + for (auto i = Loop (mask) (mask, combined_tissue); 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) * input_images[j].value() ; + for (size_t j = 0; j < n_tissue_types; ++j) { + combined_tissue.index(3) = j; + sum += scale_factors(j) * combined_tissue.value() ; } y (index++) = std::log(sum) - log_norm_value; } @@ -343,14 +347,14 @@ void run () bias_field_weights = bias_field_basis.colPivHouseholderQr().solve(y); // Generate bias field in the log domain - for (auto i = Loop (bias_field_log) (bias_field_log, mask); i; ++i) { + for (auto i = Loop (bias_field_log) (bias_field_log); i; ++i) { Eigen::Vector3 vox (bias_field_log.index(0), bias_field_log.index(1), bias_field_log.index(2)); Eigen::Vector3 pos = transform.voxel2scanner * vox; bias_field_log.value() = basis_function (pos).col(0).dot (bias_field_weights.col(0)); } // Generate bias field in the image domain - for (auto i = Loop (bias_field_log) (bias_field_log, bias_field_image, mask); i; ++i) + for (auto i = Loop (bias_field_log) (bias_field_log, bias_field_image); i; ++i) bias_field_image.value () = std::exp(bias_field_log.value()); progress++; @@ -371,7 +375,7 @@ void run () } progress++; - // compute mean of all scale factors in the log domain + // Compute mean of all scale factors in the log domain opt = get_options ("independent"); if (!opt.size()) { float mean = 0.0; @@ -382,7 +386,7 @@ void run () scale_factors.fill (mean); } - // output bias corrected and normalised tissue maps + // Output bias corrected and normalised tissue maps uint32_t total_count = 0; for (size_t i = 0; i < output_headers.size(); ++i) { uint32_t count = 1; @@ -390,12 +394,14 @@ void run () 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_image); output_image.value() = scale_factors(j, 0) * input_images[j].value() / bias_field_image.value(); + output_image.value() = std::max(output_image.value(), 0.f); } } } From 350d1ad0dbda4a26079edd0c8bd05304bef28fad Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Fri, 23 Jun 2017 14:43:40 +0100 Subject: [PATCH 019/139] tensor2metric: fix default eigenvalue/vector selection --- cmd/tensor2metric.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cmd/tensor2metric.cpp b/cmd/tensor2metric.cpp index dd97b85eb3..5426042348 100644 --- a/cmd/tensor2metric.cpp +++ b/cmd/tensor2metric.cpp @@ -133,7 +133,10 @@ class Processor { MEMALIGN(Processor) vector_img (vector_img), vals (vals), modulate (modulate), - sort_by_abs (sort_by_abs) { } + sort_by_abs (sort_by_abs) { + for (auto& n : this->vals) + --n; + } void operator() (Image& dt_img) { @@ -188,9 +191,6 @@ class Processor { MEMALIGN(Processor) std::sort (std::begin (ith_eig), std::end (ith_eig), [&eigval](size_t a, size_t b) { return std::abs(eigval[a]) > std::abs(eigval[b]); }); } - //std::cerr << ith_eig[0] << ": " << eigval[ith_eig[0]] << "; "; - //std::cerr << ith_eig[1] << ": " << eigval[ith_eig[1]] << "; "; - //std::cerr << ith_eig[2] << ": " << eigval[ith_eig[2]] << "\n"; } /* output value */ @@ -345,11 +345,9 @@ void run () vals = opt[0][0]; if (vals.empty()) throw Exception ("invalid eigenvalue/eigenvector number specifier"); - for (size_t i = 0; i < vals.size(); ++i) { + for (size_t i = 0; i < vals.size(); ++i) if (vals[i] < 1 || vals[i] > 3) throw Exception ("eigenvalue/eigenvector number is out of bounds"); - --vals[i]; - } } bool sort_by_abs = get_options ("abs").size(); From ff43970ce31817881cddcf9fa4d7c78b5a8b149b Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Mon, 26 Jun 2017 15:18:20 +1000 Subject: [PATCH 020/139] no longer needed, and a bit broken and incompatible with the new theory) --- cmd/mrmodelfield.cpp | 131 ------------------------------------------- 1 file changed, 131 deletions(-) delete mode 100644 cmd/mrmodelfield.cpp diff --git a/cmd/mrmodelfield.cpp b/cmd/mrmodelfield.cpp deleted file mode 100644 index 4700dda05d..0000000000 --- a/cmd/mrmodelfield.cpp +++ /dev/null @@ -1,131 +0,0 @@ -/* Copyright (c) 2008-2017 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 http://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)"; - - SYNOPSIS = "Model an input image using low frequency 3D polynomial basis functions"; - - DESCRIPTION - + "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, 19); - 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.colPivHouseholderQr().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 80d4f45cfed1628cc3fda20a31a64a0fab98c915 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Mon, 26 Jun 2017 15:21:52 +1000 Subject: [PATCH 021/139] docs update --- docs/reference/commands/mrmodelfield.rst | 68 ------------------- docs/reference/commands/mtlognorm.rst | 83 ++++++++++++++++++++++++ docs/reference/commands_list.rst | 4 +- 3 files changed, 85 insertions(+), 70 deletions(-) delete mode 100644 docs/reference/commands/mrmodelfield.rst create mode 100644 docs/reference/commands/mtlognorm.rst diff --git a/docs/reference/commands/mrmodelfield.rst b/docs/reference/commands/mrmodelfield.rst deleted file mode 100644 index 12fb383ed6..0000000000 --- a/docs/reference/commands/mrmodelfield.rst +++ /dev/null @@ -1,68 +0,0 @@ -.. _mrmodelfield: - -mrmodelfield -=================== - -Synopsis --------- - -Model an input image using low frequency 3D polynomial basis functions - -Usage --------- - -:: - - mrmodelfield [ options ] input output - -- *input*: the input image -- *output*: the output image representing the fit - -Description ------------ - -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-2017 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 http://www.mrtrix.org/. - - diff --git a/docs/reference/commands/mtlognorm.rst b/docs/reference/commands/mtlognorm.rst new file mode 100644 index 0000000000..72ce448cae --- /dev/null +++ b/docs/reference/commands/mtlognorm.rst @@ -0,0 +1,83 @@ +.. _mtlognorm: + +mtlognorm +=================== + +Synopsis +-------- + +Multi-Tissue Intensity Normalisation and Bias-field correction. + +Usage +-------- + +:: + + mtlognorm [ 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 +----------- + +This command inputs N number of tissue components (e.g. from multi-tissue CSD), and outputs N corrected tissue components. Intensity normalisation is performed by either determining a common global normalisation factor for all tissue types (default) or by normalising each tissue type independently with a single tissue-specific global scale factor. + +The -mask option is mandatory, and is optimally provided with a brain mask, such as the one obtained from dwi2mask earlier in the processing pipeline. + +Example usage: mtbin wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif. + +The estimated multiplicative bias field is guaranteed to have a mean of 1 over all voxels within the mask. + +Options +------- + +- **-mask image** define the mask to compute the normalisation within. This option is mandatory. + +- **-value number** specify the value to which the summed tissue compartments will be normalised to (Default: sqrt(1/(4*pi)) = 0.282094) + +- **-bias image** output the estimated bias field + +- **-independent** intensity normalise each tissue type independently + +- **-maxiter number** set the maximum number of iterations. Default(10). It will stop before the max iterations if convergence is detected + +- **-check image** check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure. However, these regions are still corrected for bias fields based on the other image data. + +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) and Thijs Dhollander (thijs.dhollander@gmail.com) + +**Copyright:** Copyright (c) 2008-2017 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 http://www.mrtrix.org/. + + diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index e2dda2196c..732b07f131 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -70,7 +70,6 @@ List of MRtrix3 commands commands/mrmath commands/mrmesh commands/mrmetric - commands/mrmodelfield commands/mrpad commands/mrregister commands/mrresize @@ -79,6 +78,7 @@ List of MRtrix3 commands commands/mrtransform commands/mrview commands/mtbin + commands/mtlognorm commands/mtnormalise commands/peaks2amp commands/sh2amp @@ -181,7 +181,6 @@ List of MRtrix3 commands :ref:`mrmath`, "Compute summary statistic on image intensities either across images, or along a specified axis of a single image" :ref:`mrmesh`, "Generate a mesh file from an image" :ref:`mrmetric`, "Computes a dissimilarity metric between two images" - :ref:`mrmodelfield`, "Model an input image using low frequency 3D polynomial basis functions" :ref:`mrpad`, "Pad an image to increase the FOV" :ref:`mrregister`, "Register two images together using a symmetric rigid, affine or non-linear transformation model" :ref:`mrresize`, "Resize an image by defining the new image resolution, voxel size or a scale factor" @@ -190,6 +189,7 @@ List of MRtrix3 commands :ref:`mrtransform`, "Apply spatial transformations to an image" :ref:`mrview`, "The MRtrix image viewer." :ref:`mtbin`, "Multi-Tissue Bias field correction and Intensity Normalisation (MTBIN)" + :ref:`mtlognorm`, "Multi-Tissue Intensity Normalisation and Bias-field correction." :ref:`mtnormalise`, "Multi-tissue normalise" :ref:`peaks2amp`, "Convert peak directions image to amplitudes" :ref:`sh2amp`, "Evaluate the amplitude of an image of spherical harmonic functions along specified directions" From 2ec0848b79a7c41b867899b536d74a43f85c6636 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Mon, 26 Jun 2017 18:18:21 +1000 Subject: [PATCH 022/139] initial mtlognorm doc changes (more to follow) --- cmd/mtlognorm.cpp | 41 +++++++++++++-------------- docs/reference/commands/mtlognorm.rst | 10 +++---- docs/reference/commands_list.rst | 2 +- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index 00f6976725..66c74ffec4 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -31,9 +31,9 @@ using namespace App; void usage () { - AUTHOR = "David Raffelt (david.raffelt@florey.edu.au), Rami Tabbara (rami.tabbara@florey.edu.au) and Thijs Dhollander (thijs.dhollander@gmail.com)"; + AUTHOR = "Thijs Dhollander (thijs.dhollander@gmail.com), Rami Tabbara (rami.tabbara@florey.edu.au) and David Raffelt (david.raffelt@florey.edu.au)"; - SYNOPSIS = "Multi-Tissue Intensity Normalisation and Bias-field correction."; + SYNOPSIS = "Multi-tissue informed log-domain intensity normalisation"; DESCRIPTION + "This command inputs N number of tissue components " @@ -43,9 +43,7 @@ void usage () + "The -mask option is mandatory, and is optimally provided with a brain mask, such as the one obtained from dwi2mask earlier in the processing pipeline." - + "Example usage: mtbin wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif." - - + "The estimated multiplicative bias field is guaranteed to have a mean of 1 over all voxels within the mask."; + + "Example usage: mtlognorm wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif."; ARGUMENTS + Argument ("input output", "list of all input and output tissue compartment files. See example usage in the description. " @@ -64,8 +62,7 @@ void usage () + 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") + + Option ("maxiter", "set the number of iterations. Default(" + str(DEFAULT_MAXITER_VALUE) + ").") + Argument ("number").type_integer() + Option ("check", "check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure. However, these regions are still corrected for bias fields based on the other image data.") @@ -84,21 +81,21 @@ FORCE_INLINE Eigen::MatrixXd basis_function (const Eigen::Vector3 pos) { 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 * z; - 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; + basis(4) = x * x; + basis(5) = y * y; + basis(6) = z * z; + basis(7) = x * y; + basis(8) = x * z; + basis(9) = y * z; + basis(10) = x * x * x; + basis(11) = y * y * y; + basis(12) = z * z * z; + basis(13) = x * x * y; + basis(14) = x * x * z; + basis(15) = y * y * x; + basis(16) = y * y * z; + basis(17) = z * z * x; + basis(18) = z * z * y; basis(19) = x * y * z; return basis; } diff --git a/docs/reference/commands/mtlognorm.rst b/docs/reference/commands/mtlognorm.rst index 72ce448cae..ec27e05614 100644 --- a/docs/reference/commands/mtlognorm.rst +++ b/docs/reference/commands/mtlognorm.rst @@ -6,7 +6,7 @@ mtlognorm Synopsis -------- -Multi-Tissue Intensity Normalisation and Bias-field correction. +Multi-tissue informed log-domain intensity normalisation Usage -------- @@ -24,9 +24,7 @@ This command inputs N number of tissue components (e.g. from multi-tissue CSD), The -mask option is mandatory, and is optimally provided with a brain mask, such as the one obtained from dwi2mask earlier in the processing pipeline. -Example usage: mtbin wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif. - -The estimated multiplicative bias field is guaranteed to have a mean of 1 over all voxels within the mask. +Example usage: mtlognorm wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif. Options ------- @@ -39,7 +37,7 @@ Options - **-independent** intensity normalise each tissue type independently -- **-maxiter number** set the maximum number of iterations. Default(10). It will stop before the max iterations if convergence is detected +- **-maxiter number** set the number of iterations. Default(10). - **-check image** check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure. However, these regions are still corrected for bias fields based on the other image data. @@ -66,7 +64,7 @@ Standard options -**Author:** David Raffelt (david.raffelt@florey.edu.au), Rami Tabbara (rami.tabbara@florey.edu.au) and Thijs Dhollander (thijs.dhollander@gmail.com) +**Author:** Thijs Dhollander (thijs.dhollander@gmail.com), Rami Tabbara (rami.tabbara@florey.edu.au) and David Raffelt (david.raffelt@florey.edu.au) **Copyright:** Copyright (c) 2008-2017 the MRtrix3 contributors. diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index 732b07f131..e7f293f775 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -189,7 +189,7 @@ List of MRtrix3 commands :ref:`mrtransform`, "Apply spatial transformations to an image" :ref:`mrview`, "The MRtrix image viewer." :ref:`mtbin`, "Multi-Tissue Bias field correction and Intensity Normalisation (MTBIN)" - :ref:`mtlognorm`, "Multi-Tissue Intensity Normalisation and Bias-field correction." + :ref:`mtlognorm`, "Multi-tissue informed log-domain intensity normalisation" :ref:`mtnormalise`, "Multi-tissue normalise" :ref:`peaks2amp`, "Convert peak directions image to amplitudes" :ref:`sh2amp`, "Evaluate the amplitude of an image of spherical harmonic functions along specified directions" From 9248b5b05005cf15d52dbf980985a35a18bbd589 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 27 Jun 2017 15:17:07 +1000 Subject: [PATCH 023/139] fixelcfestats: New option -mask This option restricts the statistical analysis to a subset of fixels, as defined by a boolean fixel data file. Calculation of the fixel-fixel connectivity matrix, data smoothing, t-statistic calculation, and stiatistical enhancement, are all only performed on those fixels within the mask. However, the output fixel directory contains all of the fixels from the input template fixel image, not just those within the mask; this retains fixel-fixel correspondence between the input template image and the output results. For fixels outside the mask, a value of NaN is written for all fixel data file outputs. --- cmd/fixelcfestats.cpp | 205 +++++++++++++++++++++++++++--------------- src/stats/cfe.cpp | 18 ++-- src/stats/cfe.h | 4 +- 3 files changed, 149 insertions(+), 78 deletions(-) diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index b6e60bfbe7..5813d18a09 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -109,7 +109,10 @@ void usage () + Argument ("threshold").type_float (0.0, 1.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); + + Argument ("value").type_float (0.0, 90.0) + + + Option ("mask", "provide a fixel data file containing a mask of those fixels to be used during processing") + + Argument ("file").type_image_in(); } @@ -118,12 +121,14 @@ void usage () template void write_fixel_output (const std::string& filename, const VectorType& data, + const vector& fixel2row, const Header& header) { + assert (header.size(0) == fixel2row.size()); auto output = Image::create (filename, header); - for (uint32_t i = 0; i < data.size(); ++i) { + for (uint32_t i = 0; i != fixel2row.size(); ++i) { output.index(0) = i; - output.value() = data[i]; + output.value() = (fixel2row[i] >= 0) ? data[fixel2row[i]] : NaN; } } @@ -148,10 +153,37 @@ void run() { const std::string input_fixel_directory = argument[0]; Header index_header = Fixel::find_index_header (input_fixel_directory); auto index_image = index_header.get_image(); - const uint32_t num_fixels = Fixel::get_number_of_fixels (index_header); CONSOLE ("number of fixels: " + str(num_fixels)); + Image mask; + uint32_t mask_fixels; + // Lookup tables that will map from input fixel index to row number, and + // from row number to fixel index + vector fixel2row (num_fixels); + opt = get_options ("mask"); + if (opt.size()) { + mask = Image::open (opt[0][0]); + Fixel::check_data_file (mask); + if (!Fixel::fixels_match (index_header, mask)) + throw Exception ("Mask image provided using -mask option does not match fixel template"); + uint32_t mask_fixels = 0; + for (auto l = Loop(mask) (mask); l; ++l) + fixel2row[mask.index(0)] = mask.value() ? mask_fixels++ : -1; + CONSOLE ("Template mask contains " + str(mask_fixels) + " fixels"); + } else { + Header data_header; + data_header.ndim() = 1; + data_header.size(0) = num_fixels; + data_header.spacing(0) = NaN; + mask = Image::scratch (data_header, "scratch fixel mask"); + for (auto l = Loop(mask) (mask); l; ++l) + mask.value() = true; + mask_fixels = num_fixels; + for (uint32_t f = 0; f != num_fixels; ++f) + fixel2row[f] = f; + } + vector positions (num_fixels); vector directions (num_fixels); @@ -163,13 +195,14 @@ void run() { // 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)); + Eigen::Vector3 vox ((default_type)index_image.index(0), (default_type)index_image.index(1), (default_type)index_image.index(2)); + vox = image_transform.voxel2scanner * vox; index_image.index(3) = 1; uint32_t offset = index_image.value(); size_t fixel_index = 0; 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; + positions[offset + fixel_index] = vox; } } } @@ -229,36 +262,34 @@ void run() { // Compute fixel-fixel connectivity vector > connectivity_matrix (num_fixels); - vector fixel_TDI (num_fixels, 0.0); + vector fixel_TDI (num_fixels, 0); const std::string track_filename = argument[4]; 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"]); + const size_t num_tracks = properties["count"].empty() ? 0 : to (properties["count"]); if (!num_tracks) throw Exception ("no tracks found in input file"); if (num_tracks < 1000000) WARN ("more than 1 million tracks should be used to ensure robust fixel-fixel connectivity"); { - using SetVoxelDir = DWI::Tractography::Mapping::SetVoxelDir; DWI::Tractography::Mapping::TrackLoader loader (track_file, num_tracks, "pre-computing fixel-fixel connectivity"); 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 (index_image, directions, fixel_TDI, connectivity_matrix, angular_threshold); + Stats::CFE::TrackProcessor tract_processor (index_image, directions, mask, fixel_TDI, connectivity_matrix, angular_threshold); Thread::run_queue ( loader, Thread::batch (DWI::Tractography::Streamline()), mapper, - Thread::batch (SetVoxelDir()), + Thread::batch (DWI::Tractography::Mapping::SetVoxelDir()), tract_processor); } track_file.close(); // Normalise connectivity matrix and threshold, pre-compute fixel-fixel weights for smoothing. - vector > smoothing_weights (num_fixels); + vector > smoothing_weights (mask_fixels); bool do_smoothing = false; - const float gaussian_const2 = 2.0 * smooth_std_dev * smooth_std_dev; float gaussian_const1 = 1.0; if (smooth_std_dev > 0.0) { @@ -269,43 +300,71 @@ void run() { { 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 connectivity_value_type connectivity = it->second.value / connectivity_value_type (fixel_TDI[fixel]); - if (connectivity < connectivity_threshold) { - connectivity_matrix[fixel].erase (it++); - } else { - if (do_smoothing) { - 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 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)); + mask.index(0) = fixel; + const uint32_t row = fixel2row[fixel]; + if (mask.value()) { + + // Here, the connectivity matrix needs to be modified to reflect the + // fact that fixel indices in the template fixel image may not + // correspond to rows in the statistical analysis + std::map new_connectivity_matrix_row; + std::map smoothing_weights_row; + connectivity_value_type sum_weights = 0.0; + + for (auto& it : connectivity_matrix[fixel]) { +#ifndef NDEBUG + // Even if this fixel is within the mask, it should still not + // connect to any fixel that is outside the mask + mask.index(0) = it.first; + assert (!mask.value()); +#endif + const connectivity_value_type connectivity = it.second.value / connectivity_value_type (fixel_TDI[fixel]); + if (connectivity >= connectivity_threshold) { + if (do_smoothing) { + 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 connectivity_value_type smoothing_weight = connectivity * gaussian_const1 * std::exp (-std::pow (distance, 2) / gaussian_const2); + if (smoothing_weight >= connectivity_threshold) { + smoothing_weights_row[fixel2row[it.first]] = smoothing_weight; + sum_weights += smoothing_weight; + } + } + // Here we pre-exponentiate each connectivity value by C + new_connectivity_matrix_row[fixel2row[it.first]] = std::pow (connectivity, cfe_c); } - // 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 - 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 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 smooth_it = smoothing_weights[fixel].begin(); smooth_it != smoothing_weights[fixel].end(); ++smooth_it) { - smooth_it->second *= norm_factor; + + // Make sure the fixel is fully connected to itself + new_connectivity_matrix_row[row] = Stats::CFE::connectivity (1.0); + smoothing_weights_row[row] = connectivity_value_type (gaussian_const1); + sum_weights += gaussian_const1; + + // Normalise smoothing weights + const connectivity_value_type norm_factor = 1.0 / sum_weights; + for (auto i : smoothing_weights_row) + i.second *= norm_factor; + + // Write the new data for this fixel to the relevant structures + // Safe to over-write connectivity_matrix, since the row in the + // statistical analysis corresponding to this fixel is always + // less than or equal to the fixel index. + connectivity_matrix[row] = std::move (new_connectivity_matrix_row); + smoothing_weights[row] = std::move (smoothing_weights_row); + + } else { + // If fixel is not in the mask, tract_processor should never assign + // any connections to it + assert (connectivity_matrix[fixel].empty()); } progress++; } } + // If the connectivity matrix has shrunk in size due to the masking of fixels, + // throw out any now-erroneous data off the end of the vector + connectivity_matrix.resize (mask_fixels); + Header output_header (header); output_header.keyval()["num permutations"] = str(num_perms); output_header.keyval()["dh"] = str(cfe_dh); @@ -318,7 +377,7 @@ void run() { // Load input data - matrix_type data (num_fixels, identifiers.size()); + matrix_type data (mask_fixels, identifiers.size()); data.setZero(); { ProgressBar progress ("loading input images", identifiers.size()); @@ -326,7 +385,7 @@ void run() { LogLevelLatch log_level (0); auto subject_data = Image::open (identifiers[subject]).with_direct_io(); - vector subject_data_vector (num_fixels, 0.0); + vector subject_data_vector (mask_fixels, 0.0); for (auto i = Loop (index_image, 0, 3)(index_image); i; ++i) { index_image.index(3) = 1; uint32_t offset = index_image.value(); @@ -334,18 +393,22 @@ void run() { for (auto f = Fixel::Loop (index_image) (subject_data); f; ++f, ++fixel_index) { if (!std::isfinite(static_cast(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(); + // Note: Data mapped from fixel index to row here; + // smoothing weights are prepared in this format + subject_data_vector[fixel2row[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) { - value += subject_data_vector[it->first] * it->second; + if (do_smoothing) { + for (size_t fixel = 0; fixel < mask_fixels; ++fixel) { + value_type value = 0.0; + for (auto i : smoothing_weights[fixel]) + value += subject_data_vector[i.first] * i.second; + data (fixel, subject) = value; } - data (fixel, subject) = value; + } else { + data.col (subject) = subject_data_vector; } progress++; } @@ -359,15 +422,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_directory, "beta" + str(i) + ".mif"), temp.row(i), output_header); + write_fixel_output (Path::join (output_fixel_directory, "beta" + str(i) + ".mif"), temp.row(i), fixel2row, output_header); ++progress; } temp = Math::Stats::GLM::abs_effect_size (data, design, contrast); ++progress; - write_fixel_output (Path::join (output_fixel_directory, "abs_effect.mif"), temp.row(0), output_header); ++progress; + write_fixel_output (Path::join (output_fixel_directory, "abs_effect.mif"), temp.row(0), fixel2row, output_header); ++progress; temp = Math::Stats::GLM::std_effect_size (data, design, contrast); ++progress; - write_fixel_output (Path::join (output_fixel_directory, "std_effect.mif"), temp.row(0), output_header); ++progress; + write_fixel_output (Path::join (output_fixel_directory, "std_effect.mif"), temp.row(0), fixel2row, output_header); ++progress; temp = Math::Stats::GLM::stdev (data, design); ++progress; - write_fixel_output (Path::join (output_fixel_directory, "std_dev.mif"), temp.row(0), output_header); + write_fixel_output (Path::join (output_fixel_directory, "std_dev.mif"), temp.row(0), fixel2row, output_header); } Math::Stats::GLMTTest glm_ttest (data, design, contrast); @@ -386,35 +449,35 @@ 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_directory, "cfe_empirical.mif"), empirical_cfe_statistic, output_header); + write_fixel_output (Path::join (output_fixel_directory, "cfe_empirical.mif"), empirical_cfe_statistic, fixel2row, output_header); } else { output_header.keyval()["nonstationary adjustment"] = str(false); } // Precompute default statistic and CFE statistic - vector_type cfe_output (num_fixels); + vector_type cfe_output (mask_fixels); std::shared_ptr cfe_output_neg; - vector_type tvalue_output (num_fixels); + vector_type tvalue_output (mask_fixels); if (compute_negative_contrast) - cfe_output_neg.reset (new vector_type (num_fixels)); + cfe_output_neg.reset (new vector_type (mask_fixels)); 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_directory, "cfe.mif"), cfe_output, output_header); - write_fixel_output (Path::join (output_fixel_directory, "tvalue.mif"), tvalue_output, output_header); + write_fixel_output (Path::join (output_fixel_directory, "cfe.mif"), cfe_output, fixel2row, output_header); + write_fixel_output (Path::join (output_fixel_directory, "tvalue.mif"), tvalue_output, fixel2row, output_header); if (compute_negative_contrast) - write_fixel_output (Path::join (output_fixel_directory, "cfe_neg.mif"), *cfe_output_neg, output_header); + write_fixel_output (Path::join (output_fixel_directory, "cfe_neg.mif"), *cfe_output_neg, fixel2row, output_header); // Perform permutation testing if (!get_options ("notest").size()) { vector_type perm_distribution (num_perms); std::shared_ptr perm_distribution_neg; - vector_type uncorrected_pvalues (num_fixels); + vector_type uncorrected_pvalues (mask_fixels); std::shared_ptr uncorrected_pvalues_neg; if (compute_negative_contrast) { perm_distribution_neg.reset (new vector_type (num_perms)); - uncorrected_pvalues_neg.reset (new vector_type (num_fixels)); + uncorrected_pvalues_neg.reset (new vector_type (mask_fixels)); } if (permutations.size()) { @@ -432,17 +495,17 @@ void run() { ProgressBar progress ("outputting final results"); save_matrix (perm_distribution, Path::join (output_fixel_directory, "perm_dist.txt")); ++progress; - vector_type pvalue_output (num_fixels); + vector_type pvalue_output (mask_fixels); Math::Stats::Permutation::statistic2pvalue (perm_distribution, cfe_output, pvalue_output); ++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; + write_fixel_output (Path::join (output_fixel_directory, "fwe_pvalue.mif"), pvalue_output, fixel2row, output_header); ++progress; + write_fixel_output (Path::join (output_fixel_directory, "uncorrected_pvalue.mif"), uncorrected_pvalues, fixel2row, output_header); ++progress; if (compute_negative_contrast) { save_matrix (*perm_distribution_neg, Path::join (output_fixel_directory, "perm_dist_neg.txt")); ++progress; - vector_type pvalue_output_neg (num_fixels); + vector_type pvalue_output_neg (mask_fixels); Math::Stats::Permutation::statistic2pvalue (*perm_distribution_neg, *cfe_output_neg, pvalue_output_neg); ++progress; - 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); + write_fixel_output (Path::join (output_fixel_directory, "fwe_pvalue_neg.mif"), pvalue_output_neg, fixel2row, output_header); ++progress; + write_fixel_output (Path::join (output_fixel_directory, "uncorrected_pvalue_neg.mif"), *uncorrected_pvalues_neg, fixel2row, output_header); } } } diff --git a/src/stats/cfe.cpp b/src/stats/cfe.cpp index 79c6bb017b..1f012fda53 100644 --- a/src/stats/cfe.cpp +++ b/src/stats/cfe.cpp @@ -25,11 +25,13 @@ namespace MR TrackProcessor::TrackProcessor (Image& fixel_indexer, const vector& fixel_directions, + Image& fixel_mask, vector& fixel_TDI, vector >& connectivity_matrix, const value_type angular_threshold) : fixel_indexer (fixel_indexer) , fixel_directions (fixel_directions), + fixel_mask (fixel_mask), fixel_TDI (fixel_TDI), connectivity_matrix (connectivity_matrix), angular_threshold_dp (std::cos (angular_threshold * (Math::pi/180.0))) { } @@ -43,22 +45,26 @@ namespace MR 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) { + uint32_t num_fixels = fixel_indexer.value(); + if (num_fixels > 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; + uint32_t last_index = first_index + num_fixels; + // Note: Streamlines can still be assigned to a fixel that is outside the mask; + // however this will not be permitted to contribute to the matrix + uint32_t closest_fixel_index = num_fixels; value_type largest_dp = 0.0; const direction_type dir (i->get_dir().normalized()); 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; - closest_fixel_index = j; + fixel_mask.index(0) = j; + if (fixel_mask.value()) + closest_fixel_index = j; } } - if (largest_dp > angular_threshold_dp) { + if (closest_fixel_index != num_fixels && largest_dp > angular_threshold_dp) { tract_fixel_indices.push_back (closest_fixel_index); fixel_TDI[closest_fixel_index]++; } diff --git a/src/stats/cfe.h b/src/stats/cfe.h index 9560e30756..1130be419e 100644 --- a/src/stats/cfe.h +++ b/src/stats/cfe.h @@ -43,7 +43,7 @@ namespace MR @{ */ - class connectivity { MEMALIGN(connectivity) + class connectivity { NOMEMALIGN public: connectivity () : value (0.0) { } connectivity (const connectivity_value_type v) : value (v) { } @@ -61,6 +61,7 @@ namespace MR public: TrackProcessor (Image& fixel_indexer, const vector& fixel_directions, + Image& fixel_mask, vector& fixel_TDI, vector >& connectivity_matrix, const value_type angular_threshold); @@ -70,6 +71,7 @@ namespace MR private: Image fixel_indexer; const vector& fixel_directions; + Image fixel_mask; vector& fixel_TDI; vector >& connectivity_matrix; const value_type angular_threshold_dp; From 11911d5ff34bf8ed54c31b59d30f325a5435e94f Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 27 Jun 2017 15:36:51 +1000 Subject: [PATCH 024/139] Docs update --- docs/reference/commands/fixelcfestats.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/reference/commands/fixelcfestats.rst b/docs/reference/commands/fixelcfestats.rst index f04ed4dfdb..209c92d544 100644 --- a/docs/reference/commands/fixelcfestats.rst +++ b/docs/reference/commands/fixelcfestats.rst @@ -62,6 +62,8 @@ Additional options for fixelcfestats - **-angle value** the max angle threshold for assigning streamline tangents to fixels (Default: 45 degrees) +- **-mask file** provide a fixel data file containing a mask of those fixels to be used during processing + Standard options ^^^^^^^^^^^^^^^^ From 7c58e1c131cf6d942279792cf2fbf68274fe70bd Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 27 Jun 2017 15:45:37 +1000 Subject: [PATCH 025/139] fixelcfestats: Fix compilation --- cmd/fixelcfestats.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 5813d18a09..54a47e3be8 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -408,7 +408,7 @@ void run() { data (fixel, subject) = value; } } else { - data.col (subject) = subject_data_vector; + data.col (subject) = Eigen::Map > (subject_data_vector.data(), mask_fixels); } progress++; } From a8acaa4d7c6641acb8b62454952887ac1c4972e8 Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Tue, 27 Jun 2017 16:45:33 +1000 Subject: [PATCH 026/139] mtlognorm: Robustness and polishing up * Add an independent max-iter for inner-loop scale normalisation * Support single-tissue input * Perform a coarse initial outlier rejection before main iteration * Remove independent option * Correctly applying scale-normalisation/bias-field in output of 4d-images * Include 'log_norm_scale' in header of output images --- cmd/mtlognorm.cpp | 194 ++++++++++++++++++++++++++-------------------- 1 file changed, 110 insertions(+), 84 deletions(-) diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index 66c74ffec4..cc772b06ba 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -28,6 +28,7 @@ using namespace App; #define DEFAULT_NORM_VALUE 0.282094 #define DEFAULT_MAXITER_VALUE 10 +#define DEFAULT_INNER_MAXITER_VALUE 10 void usage () { @@ -60,12 +61,11 @@ void usage () + Option ("bias", "output the estimated bias field") + Argument ("image").type_image_out () - + Option ("independent", "intensity normalise each tissue type independently") - + Option ("maxiter", "set the number of iterations. Default(" + str(DEFAULT_MAXITER_VALUE) + ").") + Argument ("number").type_integer() - + Option ("check", "check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure. However, these regions are still corrected for bias fields based on the other image data.") + + Option ("check", "check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure." + "However, these regions are still corrected for bias fields based on the other image data.") + Argument ("image").type_image_out (); } @@ -118,9 +118,6 @@ 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"); - ProgressBar progress ("performing intensity normalisation and bias field correction..."); vector > input_images; vector
output_headers; @@ -135,6 +132,9 @@ void run () if (i > 0) check_dimensions (input_images[0], input_images[i / 2], 0, 3); + if (input_images[i / 2].ndim () != 4) + throw Exception ("input image \"" + input_images[i / 2].name() + "\" must be 4-dimensional."); + if (Path::exists (argument[i + 1]) && !App::overwrite_files) throw Exception ("output file \"" + argument[i] + "\" already exists (use -force option to force overwrite)"); @@ -199,6 +199,7 @@ void run () const size_t max_iter = get_option_value ("maxiter", DEFAULT_MAXITER_VALUE); + const size_t max_inner_iter = DEFAULT_INNER_MAXITER_VALUE; // Initialise bias fields in both image and log domain Eigen::MatrixXd bias_field_weights (n_basis_vecs, 0); @@ -214,8 +215,58 @@ void run () Eigen::VectorXd scale_factors (n_tissue_types); Eigen::VectorXd previous_scale_factors (n_tissue_types); + scale_factors.fill(1); + size_t iter = 1; + // Store lambda-function for performing outlier-rejection. + // We perform a coarse outlier-rejection initially as well as + // a finer outlier-rejection within the inner loop when computing + // normalisation scale factors + auto outlier_rejection = [&](float outlier_range) { + + auto summed_log = Image::scratch (header_3D); + for (size_t j = 0; j < n_tissue_types; ++j) { + for (auto i = Loop (summed_log, 0, 3) (summed_log, combined_tissue, bias_field_image); i; ++i) { + combined_tissue.index(3) = j; + summed_log.value() += scale_factors(j) * combined_tissue.value() / bias_field_image.value(); + } + + summed_log.value() = std::log(summed_log.value()); + } + + threaded_copy (initial_mask, mask); + + vector summed_log_values; + for (auto i = Loop (mask) (mask, summed_log); i; ++i) { + if (mask.value()) + summed_log_values.emplace_back (summed_log.value()); + } + + num_voxels = summed_log_values.size(); + + std::sort (summed_log_values.begin(), summed_log_values.end()); + float lower_quartile = summed_log_values[std::round ((float)num_voxels * 0.25)]; + float upper_quartile = summed_log_values[std::round ((float)num_voxels * 0.75)]; + float upper_outlier_threshold = upper_quartile + outlier_range * (upper_quartile - lower_quartile); + float lower_outlier_threshold = lower_quartile - outlier_range * (upper_quartile - lower_quartile); + + + for (auto i = Loop (mask) (mask, summed_log); i; ++i) { + if (mask.value()) { + if (summed_log.value() < lower_outlier_threshold || summed_log.value() > upper_outlier_threshold) { + mask.value() = 0; + num_voxels--; + } + } + } + + if (log_level >= 3) + display (mask); + }; + + // Perform an initial outlier rejection prior to the first iteration + outlier_rejection (3.f); while (iter < max_iter) { @@ -226,38 +277,41 @@ void run () size_t norm_iter = 1; bool norm_converged = false; - while (!norm_converged && norm_iter < max_iter) { + while (!norm_converged && norm_iter < max_inner_iter) { INFO ("norm iteration: " + str(norm_iter)); - // Solve for tissue normalisation scale factors - Eigen::MatrixXd X (num_voxels, n_tissue_types); - Eigen::VectorXd y (num_voxels); - y.fill (1); - uint32_t index = 0; - - for (auto i = Loop (mask) (mask, combined_tissue, bias_field_image); i; ++i) { - if (mask.value()) { - for (size_t j = 0; j < n_tissue_types; ++j) { - combined_tissue.index (3) = j; - X (index, j) = combined_tissue.value() / bias_field_image.value(); + if (n_tissue_types > 1) { + + // Solve for tissue normalisation scale factors + Eigen::MatrixXd X (num_voxels, n_tissue_types); + Eigen::VectorXd y (num_voxels); + y.fill (1); + uint32_t index = 0; + + for (auto i = Loop (mask) (mask, combined_tissue, bias_field_image); i; ++i) { + if (mask.value()) { + for (size_t j = 0; j < n_tissue_types; ++j) { + combined_tissue.index (3) = j; + X (index, j) = combined_tissue.value() / bias_field_image.value(); + } + ++index; } - ++index; } - } - scale_factors = X.colPivHouseholderQr().solve(y); + scale_factors = X.colPivHouseholderQr().solve(y); - // Ensure our scale factors satisfy the condition that sum(log(scale_factors)) = 0 - double log_sum = 0.f; - for (size_t j = 0; j < n_tissue_types; ++j) { - if (scale_factors(j) <= 0.0) - throw Exception ("Non-positive tissue intensity normalisation scale factor was computed." - " Tissue index: " + str(j) + " Scale factor: " + str(scale_factors(j)) + - " Needs to be strictly positive!"); - log_sum += std::log (scale_factors(j)); + // Ensure our scale factors satisfy the condition that sum(log(scale_factors)) = 0 + double log_sum = 0.f; + for (size_t j = 0; j < n_tissue_types; ++j) { + if (scale_factors(j) <= 0.0) + throw Exception ("Non-positive tissue intensity normalisation scale factor was computed." + " Tissue index: " + str(j) + " Scale factor: " + str(scale_factors(j)) + + " Needs to be strictly positive!"); + log_sum += std::log (scale_factors(j)); + } + scale_factors /= std::exp (log_sum / n_tissue_types); } - scale_factors /= std::exp (log_sum / n_tissue_types); // Check for convergence if (iter > 1) { @@ -270,45 +324,7 @@ void run () // Perform outlier rejection on log-domain of summed images if (!norm_converged) { - - auto summed_log = Image::scratch (header_3D); - for (size_t j = 0; j < n_tissue_types; ++j) { - for (auto i = Loop (summed_log, 0, 3) (summed_log, combined_tissue, bias_field_image); i; ++i) { - combined_tissue.index(3) = j; - summed_log.value() += scale_factors(j) * combined_tissue.value() / bias_field_image.value(); - } - - summed_log.value() = std::log(summed_log.value()); - } - - refine_mask (summed_log, initial_mask, mask); - - vector summed_log_values; - for (auto i = Loop (mask) (mask, summed_log); i; ++i) { - if (mask.value()) - summed_log_values.emplace_back (summed_log.value()); - } - - num_voxels = summed_log_values.size(); - - std::sort (summed_log_values.begin(), summed_log_values.end()); - float lower_quartile = summed_log_values[std::round ((float)num_voxels * 0.25)]; - float upper_quartile = summed_log_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_log); i; ++i) { - if (mask.value()) { - if (summed_log.value() < lower_outlier_threshold || summed_log.value() > upper_outlier_threshold) { - mask.value() = 0; - num_voxels--; - } - } - } - - if (log_level >= 3) - display (mask); + outlier_rejection(1.6f); } previous_scale_factors = scale_factors; @@ -372,16 +388,6 @@ void run () } progress++; - // 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 uint32_t total_count = 0; @@ -392,13 +398,33 @@ void run () total_count += count; } + // Compute log-norm scale + float log_norm_scale { 0.f }; + if (num_voxels) { + for (auto i = Loop (0,3) (mask, bias_field_log); i; ++i) { + if (mask.value ()) + log_norm_scale += bias_field_log.value (); + } + + log_norm_scale = std::exp(log_norm_scale / (float)num_voxels); + } + + for (size_t j = 0; j < output_filenames.size(); ++j) { - output_headers[j].keyval()["normalisation_scale_factor"] = str(scale_factors(j, 0)); + output_headers[j].keyval()["log_norm_scale"] = str(log_norm_scale); 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_image); - output_image.value() = scale_factors(j, 0) * input_images[j].value() / bias_field_image.value(); - output_image.value() = std::max(output_image.value(), 0.f); + const size_t n_vols = input_images[j].size(3); + const Eigen::VectorXf zero_vec = Eigen::VectorXf::Zero (n_vols); + + for (auto i = Loop (0,3) (output_image, input_images[j], bias_field_image); i; ++i) { + input_images[j].index(3) = 0; + + float dc = scale_factors(j) * input_images[j].value() / bias_field_image.value(); + + if (dc < 0.f) + output_image.row(3) = zero_vec; + else + output_image.row(3) = scale_factors(j) * Eigen::VectorXf{input_images[j].row(3)} / bias_field_image.value(); } } } From bb6265473a28c8c448a47ee270b061776cbf4d1d Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 27 Jun 2017 16:57:19 +1000 Subject: [PATCH 027/139] docs update --- docs/reference/commands/mtlognorm.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/reference/commands/mtlognorm.rst b/docs/reference/commands/mtlognorm.rst index ec27e05614..6a3a6ab922 100644 --- a/docs/reference/commands/mtlognorm.rst +++ b/docs/reference/commands/mtlognorm.rst @@ -35,11 +35,9 @@ Options - **-bias image** output the estimated bias field -- **-independent** intensity normalise each tissue type independently - - **-maxiter number** set the number of iterations. Default(10). -- **-check image** check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure. However, these regions are still corrected for bias fields based on the other image data. +- **-check image** check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure.However, these regions are still corrected for bias fields based on the other image data. Standard options ^^^^^^^^^^^^^^^^ From 1c388e3f25fc4a537bfc187b3dcf709a473ec9fa Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 27 Jun 2017 17:26:04 +1000 Subject: [PATCH 028/139] mtlognorm tweaks --- cmd/mtlognorm.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index cc772b06ba..826e6db658 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -324,7 +324,7 @@ void run () // Perform outlier rejection on log-domain of summed images if (!norm_converged) { - outlier_rejection(1.6f); + outlier_rejection(1.5f); } previous_scale_factors = scale_factors; @@ -399,19 +399,19 @@ void run () } // Compute log-norm scale - float log_norm_scale { 0.f }; + float lognorm_scale { 0.f }; if (num_voxels) { for (auto i = Loop (0,3) (mask, bias_field_log); i; ++i) { if (mask.value ()) - log_norm_scale += bias_field_log.value (); + lognorm_scale += bias_field_log.value (); } - log_norm_scale = std::exp(log_norm_scale / (float)num_voxels); + lognorm_scale = std::exp(lognorm_scale / (float)num_voxels); } for (size_t j = 0; j < output_filenames.size(); ++j) { - output_headers[j].keyval()["log_norm_scale"] = str(log_norm_scale); + output_headers[j].keyval()["lognorm_scale"] = str(lognorm_scale); auto output_image = Image::create (output_filenames[j], output_headers[j]); const size_t n_vols = input_images[j].size(3); const Eigen::VectorXf zero_vec = Eigen::VectorXf::Zero (n_vols); From c0a6e362e211496eb8f6b33ab03c0bd96169a7dc Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 27 Jun 2017 18:13:15 +1000 Subject: [PATCH 029/139] remove independent scaling --- cmd/mtlognorm.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index 826e6db658..3c7d5f8ffb 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -419,12 +419,12 @@ void run () for (auto i = Loop (0,3) (output_image, input_images[j], bias_field_image); i; ++i) { input_images[j].index(3) = 0; - float dc = scale_factors(j) * input_images[j].value() / bias_field_image.value(); + float dc = input_images[j].value() / bias_field_image.value(); if (dc < 0.f) output_image.row(3) = zero_vec; else - output_image.row(3) = scale_factors(j) * Eigen::VectorXf{input_images[j].row(3)} / bias_field_image.value(); + output_image.row(3) = Eigen::VectorXf{input_images[j].row(3)} / bias_field_image.value(); } } } From fcaf707e9d9fadd97964b9537812f7bd3f72cf14 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 27 Jun 2017 18:34:31 +1000 Subject: [PATCH 030/139] mtlognorm iterations tweaks --- cmd/mtlognorm.cpp | 8 ++++---- docs/reference/commands/mtlognorm.rst | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index 3c7d5f8ffb..9262de3bd9 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -27,8 +27,8 @@ using namespace MR; using namespace App; #define DEFAULT_NORM_VALUE 0.282094 -#define DEFAULT_MAXITER_VALUE 10 -#define DEFAULT_INNER_MAXITER_VALUE 10 +#define DEFAULT_MAIN_ITER_VALUE 15 +#define DEFAULT_INNER_MAXITER_VALUE 21 void usage () { @@ -61,7 +61,7 @@ void usage () + Option ("bias", "output the estimated bias field") + Argument ("image").type_image_out () - + Option ("maxiter", "set the number of iterations. Default(" + str(DEFAULT_MAXITER_VALUE) + ").") + + Option ("maxiter", "set the number of iterations. Default(" + str(DEFAULT_MAIN_ITER_VALUE) + ").") + Argument ("number").type_integer() + Option ("check", "check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure." @@ -197,7 +197,7 @@ void run () const float log_norm_value = std::log (normalisation_value); - const size_t max_iter = get_option_value ("maxiter", DEFAULT_MAXITER_VALUE); + const size_t max_iter = get_option_value ("maxiter", DEFAULT_MAIN_ITER_VALUE); const size_t max_inner_iter = DEFAULT_INNER_MAXITER_VALUE; diff --git a/docs/reference/commands/mtlognorm.rst b/docs/reference/commands/mtlognorm.rst index 6a3a6ab922..3ef7b1ae79 100644 --- a/docs/reference/commands/mtlognorm.rst +++ b/docs/reference/commands/mtlognorm.rst @@ -35,7 +35,7 @@ Options - **-bias image** output the estimated bias field -- **-maxiter number** set the number of iterations. Default(10). +- **-maxiter number** set the number of iterations. Default(15). - **-check image** check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure.However, these regions are still corrected for bias fields based on the other image data. From 30d6b712bb25087d38b2319965d764b3863c8103 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 27 Jun 2017 19:14:20 +1000 Subject: [PATCH 031/139] mtlognorm iteration count fix --- cmd/mtlognorm.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index 9262de3bd9..02f7f00381 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -268,7 +268,7 @@ void run () // Perform an initial outlier rejection prior to the first iteration outlier_rejection (3.f); - while (iter < max_iter) { + while (iter <= max_iter) { INFO ("iteration: " + str(iter)); @@ -277,7 +277,7 @@ void run () size_t norm_iter = 1; bool norm_converged = false; - while (!norm_converged && norm_iter < max_inner_iter) { + while (!norm_converged && norm_iter <= max_inner_iter) { INFO ("norm iteration: " + str(norm_iter)); From cf720f392e1fb804ae0de3aef984cfb0b540bf17 Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Wed, 28 Jun 2017 10:59:33 +1000 Subject: [PATCH 032/139] mtlognorm: Changing scale factor convergence criterion * Looking at difference between mask affected by outlier rejection --- cmd/mtlognorm.cpp | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index 02f7f00381..00717aefb7 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -153,6 +153,7 @@ void run () auto orig_mask = Image::open (opt[0][0]); auto initial_mask = Image::scratch (orig_mask); auto mask = Image::scratch (orig_mask); + auto prev_mask = Image::scratch (orig_mask); auto summed = Image::scratch (header_3D); for (size_t j = 0; j < input_images.size(); ++j) { @@ -213,8 +214,6 @@ void run () } Eigen::VectorXd scale_factors (n_tissue_types); - Eigen::VectorXd previous_scale_factors (n_tissue_types); - scale_factors.fill(1); size_t iter = 1; @@ -263,6 +262,8 @@ void run () if (log_level >= 3) display (mask); + + threaded_copy (mask, prev_mask); }; // Perform an initial outlier rejection prior to the first iteration @@ -313,21 +314,17 @@ void run () scale_factors /= std::exp (log_sum / n_tissue_types); } - // Check for convergence - if (iter > 1) { - Eigen::VectorXd diff = previous_scale_factors.array() - scale_factors.array(); - diff = diff.array().abs() / previous_scale_factors.array(); - INFO ("percentage change in estimated scale factors: " + str(diff.mean() * 100)); - if (diff.mean() < 0.001) - norm_converged = true; - } - // Perform outlier rejection on log-domain of summed images - if (!norm_converged) { - outlier_rejection(1.5f); - } + outlier_rejection(1.5f); - previous_scale_factors = scale_factors; + // Check for convergence + norm_converged = true; + for (auto i = Loop (mask) (mask, prev_mask); i; ++i) { + if (mask.value() != prev_mask.value()) { + norm_converged = false; + break; + } + } norm_iter++; } From e42403e542c3be1f740cbfd2df091e19bce79b0e Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Wed, 28 Jun 2017 11:29:16 +1000 Subject: [PATCH 033/139] mtlognorm: Fixing convergence criterion bug when computing scale normalisation factors * Make sure we compare prev_mask to current mask before we cache the current mask. Genius! --- cmd/mtlognorm.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index 00717aefb7..32f6a3d444 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -262,13 +262,13 @@ void run () if (log_level >= 3) display (mask); - - threaded_copy (mask, prev_mask); }; // Perform an initial outlier rejection prior to the first iteration outlier_rejection (3.f); + threaded_copy (mask, prev_mask); + while (iter <= max_iter) { INFO ("iteration: " + str(iter)); @@ -314,6 +314,8 @@ void run () scale_factors /= std::exp (log_sum / n_tissue_types); } + INFO ("scale factors: " + str(scale_factors.transpose())); + // Perform outlier rejection on log-domain of summed images outlier_rejection(1.5f); @@ -326,13 +328,12 @@ void run () } } + threaded_copy (mask, prev_mask); + norm_iter++; } - INFO ("scale factors: " + str(scale_factors.transpose())); - - // Solve for bias field weights in the log domain Transform transform (mask); Eigen::MatrixXd bias_field_basis (num_voxels, n_basis_vecs); From a4f9a2ae53444b0d7a50afa78d057f53ab8047d3 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Wed, 28 Jun 2017 11:47:57 +1000 Subject: [PATCH 034/139] inner max iterations tweak based on new convergence observations --- cmd/mtlognorm.cpp | 4 ++-- docs/reference/commands/mtlognorm.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index 32f6a3d444..278dd30aed 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -26,9 +26,9 @@ using namespace MR; using namespace App; -#define DEFAULT_NORM_VALUE 0.282094 +#define DEFAULT_NORM_VALUE 0.28209479177 #define DEFAULT_MAIN_ITER_VALUE 15 -#define DEFAULT_INNER_MAXITER_VALUE 21 +#define DEFAULT_INNER_MAXITER_VALUE 7 void usage () { diff --git a/docs/reference/commands/mtlognorm.rst b/docs/reference/commands/mtlognorm.rst index 3ef7b1ae79..4b1d014909 100644 --- a/docs/reference/commands/mtlognorm.rst +++ b/docs/reference/commands/mtlognorm.rst @@ -31,7 +31,7 @@ Options - **-mask image** define the mask to compute the normalisation within. This option is mandatory. -- **-value number** specify the value to which the summed tissue compartments will be normalised to (Default: sqrt(1/(4*pi)) = 0.282094) +- **-value number** specify the value to which the summed tissue compartments will be normalised to (Default: sqrt(1/(4*pi)) = 0.282095) - **-bias image** output the estimated bias field From 585ed8f4195c1268b2e3dac5cb3660e76f24904b Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Wed, 28 Jun 2017 15:06:15 +1000 Subject: [PATCH 035/139] mtlognorm: When iterating over multiple input-images concurrently, make sure we avoid stride mismatches --- cmd/mtlognorm.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index 278dd30aed..7b9f709c6b 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -157,7 +157,7 @@ void run () 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) + for (auto i = Loop (0, 3) (summed, input_images[j]); i; ++i) summed.value() += input_images[j].value(); progress++; } @@ -226,7 +226,7 @@ void run () auto summed_log = Image::scratch (header_3D); for (size_t j = 0; j < n_tissue_types; ++j) { - for (auto i = Loop (summed_log, 0, 3) (summed_log, combined_tissue, bias_field_image); i; ++i) { + for (auto i = Loop (0, 3) (summed_log, combined_tissue, bias_field_image); i; ++i) { combined_tissue.index(3) = j; summed_log.value() += scale_factors(j) * combined_tissue.value() / bias_field_image.value(); } @@ -237,7 +237,7 @@ void run () threaded_copy (initial_mask, mask); vector summed_log_values; - for (auto i = Loop (mask) (mask, summed_log); i; ++i) { + for (auto i = Loop (0, 3) (mask, summed_log); i; ++i) { if (mask.value()) summed_log_values.emplace_back (summed_log.value()); } @@ -251,7 +251,7 @@ void run () float lower_outlier_threshold = lower_quartile - outlier_range * (upper_quartile - lower_quartile); - for (auto i = Loop (mask) (mask, summed_log); i; ++i) { + for (auto i = Loop (0, 3) (mask, summed_log); i; ++i) { if (mask.value()) { if (summed_log.value() < lower_outlier_threshold || summed_log.value() > upper_outlier_threshold) { mask.value() = 0; @@ -290,7 +290,7 @@ void run () y.fill (1); uint32_t index = 0; - for (auto i = Loop (mask) (mask, combined_tissue, bias_field_image); i; ++i) { + for (auto i = Loop (0, 3) (mask, combined_tissue, bias_field_image); i; ++i) { if (mask.value()) { for (size_t j = 0; j < n_tissue_types; ++j) { combined_tissue.index (3) = j; @@ -321,7 +321,7 @@ void run () // Check for convergence norm_converged = true; - for (auto i = Loop (mask) (mask, prev_mask); i; ++i) { + for (auto i = Loop (0, 3) (mask, prev_mask); i; ++i) { if (mask.value() != prev_mask.value()) { norm_converged = false; break; @@ -340,7 +340,7 @@ void run () Eigen::MatrixXd X (num_voxels, n_tissue_types); Eigen::VectorXd y (num_voxels); uint32_t index = 0; - for (auto i = Loop (mask) (mask, combined_tissue); i; ++i) { + for (auto i = Loop (0, 3) (mask, combined_tissue); i; ++i) { if (mask.value()) { Eigen::Vector3 vox (mask.index(0), mask.index(1), mask.index(2)); Eigen::Vector3 pos = transform.voxel2scanner * vox; @@ -358,14 +358,14 @@ void run () bias_field_weights = bias_field_basis.colPivHouseholderQr().solve(y); // Generate bias field in the log domain - for (auto i = Loop (bias_field_log) (bias_field_log); i; ++i) { + for (auto i = Loop (0, 3) (bias_field_log); i; ++i) { Eigen::Vector3 vox (bias_field_log.index(0), bias_field_log.index(1), bias_field_log.index(2)); Eigen::Vector3 pos = transform.voxel2scanner * vox; bias_field_log.value() = basis_function (pos).col(0).dot (bias_field_weights.col(0)); } // Generate bias field in the image domain - for (auto i = Loop (bias_field_log) (bias_field_log, bias_field_image); i; ++i) + for (auto i = Loop (0, 3) (bias_field_log, bias_field_image); i; ++i) bias_field_image.value () = std::exp(bias_field_log.value()); progress++; @@ -417,9 +417,7 @@ void run () for (auto i = Loop (0,3) (output_image, input_images[j], bias_field_image); i; ++i) { input_images[j].index(3) = 0; - float dc = input_images[j].value() / bias_field_image.value(); - - if (dc < 0.f) + if (input_images[j].value() < 0.f) output_image.row(3) = zero_vec; else output_image.row(3) = Eigen::VectorXf{input_images[j].row(3)} / bias_field_image.value(); From 3a6aded2603916acabecc9eb5e5eb8d0d3389a0e Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 28 Jun 2017 15:12:34 +1000 Subject: [PATCH 036/139] fixelcfestats: Various fixels for -mask option Also includes a bug fix where opting to not smooth the input data would likely have resulted in an error. --- cmd/fixelcfestats.cpp | 59 +++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 54a47e3be8..d446cf83a9 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -124,7 +124,7 @@ void write_fixel_output (const std::string& filename, const vector& fixel2row, const Header& header) { - assert (header.size(0) == fixel2row.size()); + assert (size_t(header.size(0)) == fixel2row.size()); auto output = Image::create (filename, header); for (uint32_t i = 0; i != fixel2row.size(); ++i) { output.index(0) = i; @@ -153,13 +153,15 @@ void run() { const std::string input_fixel_directory = argument[0]; Header index_header = Fixel::find_index_header (input_fixel_directory); auto index_image = index_header.get_image(); + const uint32_t num_fixels = Fixel::get_number_of_fixels (index_header); CONSOLE ("number of fixels: " + str(num_fixels)); Image mask; uint32_t mask_fixels; - // Lookup tables that will map from input fixel index to row number, and - // from row number to fixel index + // Lookup table that maps from input fixel index to row number + // Note that if a fixel is masked out, it will have a value of -1 + // in this array vector fixel2row (num_fixels); opt = get_options ("mask"); if (opt.size()) { @@ -170,15 +172,20 @@ void run() { uint32_t mask_fixels = 0; for (auto l = Loop(mask) (mask); l; ++l) fixel2row[mask.index(0)] = mask.value() ? mask_fixels++ : -1; - CONSOLE ("Template mask contains " + str(mask_fixels) + " fixels"); + CONSOLE ("Mask contains " + str(mask_fixels) + " fixels"); } else { Header data_header; - data_header.ndim() = 1; + data_header.ndim() = 3; data_header.size(0) = num_fixels; - data_header.spacing(0) = NaN; + data_header.size(1) = 1; + data_header.size(2) = 1; + data_header.spacing(0) = data_header.spacing(1) = data_header.spacing(2) = NaN; + data_header.stride(0) = 1; data_header.stride(1) = 2; data_header.stride(2) = 3; mask = Image::scratch (data_header, "scratch fixel mask"); - for (auto l = Loop(mask) (mask); l; ++l) + for (uint32_t f = 0; f != num_fixels; ++f) { + mask.index(0) = f; mask.value() = true; + } mask_fixels = num_fixels; for (uint32_t f = 0; f != num_fixels; ++f) fixel2row[f] = f; @@ -290,6 +297,7 @@ void run() { // Normalise connectivity matrix and threshold, pre-compute fixel-fixel weights for smoothing. vector > smoothing_weights (mask_fixels); bool do_smoothing = false; + const float gaussian_const2 = 2.0 * smooth_std_dev * smooth_std_dev; float gaussian_const1 = 1.0; if (smooth_std_dev > 0.0) { @@ -301,14 +309,14 @@ void run() { ProgressBar progress ("normalising and thresholding fixel-fixel connectivity matrix", num_fixels); for (uint32_t fixel = 0; fixel < num_fixels; ++fixel) { mask.index(0) = fixel; - const uint32_t row = fixel2row[fixel]; + const int32_t row = fixel2row[fixel]; + if (mask.value()) { // Here, the connectivity matrix needs to be modified to reflect the // fact that fixel indices in the template fixel image may not // correspond to rows in the statistical analysis std::map new_connectivity_matrix_row; - std::map smoothing_weights_row; connectivity_value_type sum_weights = 0.0; for (auto& it : connectivity_matrix[fixel]) { @@ -316,7 +324,7 @@ void run() { // Even if this fixel is within the mask, it should still not // connect to any fixel that is outside the mask mask.index(0) = it.first; - assert (!mask.value()); + assert (mask.value()); #endif const connectivity_value_type connectivity = it.second.value / connectivity_value_type (fixel_TDI[fixel]); if (connectivity >= connectivity_threshold) { @@ -326,7 +334,7 @@ void run() { Math::pow2 (positions[fixel][2] - positions[it.first][2])); const connectivity_value_type smoothing_weight = connectivity * gaussian_const1 * std::exp (-std::pow (distance, 2) / gaussian_const2); if (smoothing_weight >= connectivity_threshold) { - smoothing_weights_row[fixel2row[it.first]] = smoothing_weight; + smoothing_weights[row][fixel2row[it.first]] = smoothing_weight; sum_weights += smoothing_weight; } } @@ -337,26 +345,27 @@ void run() { // Make sure the fixel is fully connected to itself new_connectivity_matrix_row[row] = Stats::CFE::connectivity (1.0); - smoothing_weights_row[row] = connectivity_value_type (gaussian_const1); - sum_weights += gaussian_const1; + smoothing_weights[row][row] = connectivity_value_type(gaussian_const1); + sum_weights += connectivity_value_type(gaussian_const1); // Normalise smoothing weights - const connectivity_value_type norm_factor = 1.0 / sum_weights; - for (auto i : smoothing_weights_row) + const connectivity_value_type norm_factor = connectivity_value_type(1.0) / sum_weights; + for (auto i : smoothing_weights[row]) i.second *= norm_factor; - // Write the new data for this fixel to the relevant structures - // Safe to over-write connectivity_matrix, since the row in the - // statistical analysis corresponding to this fixel is always - // less than or equal to the fixel index. + // Write the new connectivity matrix data to the relevant structure + // Note that due to fixel masking, this may not be the same row as + // the fixel index connectivity_matrix[row] = std::move (new_connectivity_matrix_row); - smoothing_weights[row] = std::move (smoothing_weights_row); - + // Force deallocation of memory used + std::map().swap (new_connectivity_matrix_row); } else { // If fixel is not in the mask, tract_processor should never assign // any connections to it assert (connectivity_matrix[fixel].empty()); + } + progress++; } } @@ -365,6 +374,7 @@ void run() { // throw out any now-erroneous data off the end of the vector connectivity_matrix.resize (mask_fixels); + Header output_header (header); output_header.keyval()["num permutations"] = str(num_perms); output_header.keyval()["dh"] = str(cfe_dh); @@ -393,9 +403,10 @@ void run() { for (auto f = Fixel::Loop (index_image) (subject_data); f; ++f, ++fixel_index) { if (!std::isfinite(static_cast(subject_data.value()))) throw Exception ("subject data file " + identifiers[subject] + " contains non-finite value: " + str(subject_data.value())); - // Note: Data mapped from fixel index to row here; - // smoothing weights are prepared in this format - subject_data_vector[fixel2row[offset+fixel_index]] = subject_data.value(); + // Note that immediately on import, data are re-arranged according to fixel mask + const int32_t row = fixel2row[offset+fixel_index]; + if (row >= 0) + subject_data_vector[row] = subject_data.value(); } } From 6cad81b0dd819bc2ff03030974f720b0cf864e79 Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Wed, 28 Jun 2017 15:43:39 +1000 Subject: [PATCH 037/139] mtlognorm: Adding support for 3d (or lower) input images * Elevate all input images to 4d via replicate adapter --- cmd/mtlognorm.cpp | 51 ++++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index 7b9f709c6b..58b25a434a 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -22,6 +22,7 @@ #include "transform.h" #include "math/least_squares.h" #include "algo/threaded_copy.h" +#include "adapter/replicate.h" using namespace MR; using namespace App; @@ -119,7 +120,11 @@ void run () throw Exception ("The number of input arguments must be even. There must be an output file provided for every input tissue image"); ProgressBar progress ("performing intensity normalisation and bias field correction..."); - vector > input_images; + + using ImageType = Image; + using MaskType = Image; + + vector> input_images; vector
output_headers; vector output_filenames; @@ -127,18 +132,28 @@ void run () // Open input images and prepare output image headers for (size_t i = 0; i < argument.size(); i += 2) { progress++; - input_images.emplace_back (Image::open (argument[i])); + + auto image = ImageType::open (argument[i]); + + if (image.ndim () > 4) + throw Exception ("input image \"" + image.name() + "\" must contain 4 dimensions or less."); + + // Elevate image dimensions to ensure it is 4-dimensional + // e.g. x,y,z -> x,y,z,1 + // This ensures consistency across multiple tissue input images + Header h_image4d (image); + h_image4d.ndim() = 4; + + Adapter::Replicate image4d (image, h_image4d); + input_images.emplace_back (image4d); if (i > 0) check_dimensions (input_images[0], input_images[i / 2], 0, 3); - if (input_images[i / 2].ndim () != 4) - throw Exception ("input image \"" + input_images[i / 2].name() + "\" must be 4-dimensional."); - if (Path::exists (argument[i + 1]) && !App::overwrite_files) throw Exception ("output file \"" + argument[i] + "\" already exists (use -force option to force overwrite)"); - output_headers.emplace_back (Header::open (argument[i])); + output_headers.emplace_back (h_image4d); output_filenames.emplace_back (argument[i + 1]); } @@ -150,12 +165,12 @@ void run () header_3D.ndim() = 3; auto opt = get_options ("mask"); - auto orig_mask = Image::open (opt[0][0]); - auto initial_mask = Image::scratch (orig_mask); - auto mask = Image::scratch (orig_mask); - auto prev_mask = Image::scratch (orig_mask); + auto orig_mask = MaskType::open (opt[0][0]); + auto initial_mask = MaskType::scratch (orig_mask); + auto mask = MaskType::scratch (orig_mask); + auto prev_mask = MaskType::scratch (orig_mask); - auto summed = Image::scratch (header_3D); + auto summed = ImageType::scratch (header_3D); for (size_t j = 0; j < input_images.size(); ++j) { for (auto i = Loop (0, 3) (summed, input_images[j]); i; ++i) summed.value() += input_images[j].value(); @@ -171,7 +186,7 @@ void run () Header h_combined_tissue (input_images[0]); h_combined_tissue.ndim () = 4; h_combined_tissue.size (3) = n_tissue_types; - auto combined_tissue = Image::scratch (h_combined_tissue, "Packed tissue components"); + auto combined_tissue = ImageType::scratch (h_combined_tissue, "Packed tissue components"); for (size_t i = 0; i < n_tissue_types; ++i) { combined_tissue.index (3) = i; @@ -205,8 +220,8 @@ void run () // Initialise bias fields in both image and log domain Eigen::MatrixXd bias_field_weights (n_basis_vecs, 0); - auto bias_field_image = Image::scratch (header_3D); - auto bias_field_log = Image::scratch (header_3D); + auto bias_field_image = ImageType::scratch (header_3D); + auto bias_field_log = ImageType::scratch (header_3D); for (auto i = Loop(bias_field_log) (bias_field_image, bias_field_log); i; ++i) { bias_field_image.value() = 1.f; @@ -224,7 +239,7 @@ void run () // normalisation scale factors auto outlier_rejection = [&](float outlier_range) { - auto summed_log = Image::scratch (header_3D); + auto summed_log = ImageType::scratch (header_3D); for (size_t j = 0; j < n_tissue_types; ++j) { for (auto i = Loop (0, 3) (summed_log, combined_tissue, bias_field_image); i; ++i) { combined_tissue.index(3) = j; @@ -374,14 +389,14 @@ void run () opt = get_options ("bias"); if (opt.size()) { - auto bias_field_output = Image::create (opt[0][0], header_3D); + auto bias_field_output = ImageType::create (opt[0][0], header_3D); threaded_copy (bias_field_image, bias_field_output); } progress++; opt = get_options ("check"); if (opt.size()) { - auto mask_output = Image::create (opt[0][0], mask); + auto mask_output = ImageType::create (opt[0][0], mask); threaded_copy (mask, mask_output); } progress++; @@ -410,7 +425,7 @@ void run () for (size_t j = 0; j < output_filenames.size(); ++j) { output_headers[j].keyval()["lognorm_scale"] = str(lognorm_scale); - auto output_image = Image::create (output_filenames[j], output_headers[j]); + auto output_image = ImageType::create (output_filenames[j], output_headers[j]); const size_t n_vols = input_images[j].size(3); const Eigen::VectorXf zero_vec = Eigen::VectorXf::Zero (n_vols); From 36224e5445fbd88ed50c14a8c3d4b8f774e0273a Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 29 Jun 2017 11:32:30 +1000 Subject: [PATCH 038/139] fixelcfestats: Change type of normalised connectivity matrix On testing prior changes to fixelcfestats to support the -mask option, it was found to run extremely slowly, even when not using the -mask option. The suspected cause was that during the matrix normalisation, each row of the connectivity matrix (a map) was being re-constructed from scratch (due to the possibility of fixel indices changing), and hence what was a balanced binary search tree would become essentially a sorted doubly-linked-list. Now, each row of the connectivity matrix is instead converted to essentially a vector>, since it is no longer necessary to have fast lookup for an arbitrary fixel index. This should reduce memory requirement after matrix construction has completed, and will also hopefully improve cache performance since the connectivity data for each fixel will be serialised. There are also various little fixes and tweaks to polish off additions for supporting the -mask option. --- cmd/fixelcfestats.cpp | 76 ++++++++++++++++++++++--------------------- src/stats/cfe.cpp | 30 ++++++++--------- src/stats/cfe.h | 39 ++++++++++++++++++---- 3 files changed, 86 insertions(+), 59 deletions(-) diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index d446cf83a9..ac06fb7ac6 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -39,6 +39,7 @@ using namespace MR::DWI::Tractography::Mapping; using namespace MR::Math::Stats; using Stats::CFE::direction_type; using Stats::CFE::connectivity_value_type; +using Stats::CFE::index_type; #define DEFAULT_CFE_DH 0.1 #define DEFAULT_CFE_E 2.0 @@ -50,7 +51,7 @@ using Stats::CFE::connectivity_value_type; 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)"; SYNOPSIS = "Fixel-based analysis using connectivity-based fixel enhancement and non-parametric permutation testing"; @@ -126,7 +127,7 @@ void write_fixel_output (const std::string& filename, { assert (size_t(header.size(0)) == fixel2row.size()); auto output = Image::create (filename, header); - for (uint32_t i = 0; i != fixel2row.size(); ++i) { + for (size_t i = 0; i != fixel2row.size(); ++i) { output.index(0) = i; output.value() = (fixel2row[i] >= 0) ? data[fixel2row[i]] : NaN; } @@ -152,16 +153,16 @@ void run() { const std::string input_fixel_directory = argument[0]; Header index_header = Fixel::find_index_header (input_fixel_directory); - auto index_image = index_header.get_image(); + auto index_image = index_header.get_image(); - const uint32_t num_fixels = Fixel::get_number_of_fixels (index_header); + const index_type num_fixels = Fixel::get_number_of_fixels (index_header); CONSOLE ("number of fixels: " + str(num_fixels)); Image mask; - uint32_t mask_fixels; + index_type mask_fixels; // Lookup table that maps from input fixel index to row number // Note that if a fixel is masked out, it will have a value of -1 - // in this array + // in this array; hence require a signed integer type vector fixel2row (num_fixels); opt = get_options ("mask"); if (opt.size()) { @@ -169,8 +170,8 @@ void run() { Fixel::check_data_file (mask); if (!Fixel::fixels_match (index_header, mask)) throw Exception ("Mask image provided using -mask option does not match fixel template"); - uint32_t mask_fixels = 0; - for (auto l = Loop(mask) (mask); l; ++l) + mask_fixels = 0; + for (mask.index(0) = 0; mask.index(0) != num_fixels; ++mask.index(0)) fixel2row[mask.index(0)] = mask.value() ? mask_fixels++ : -1; CONSOLE ("Mask contains " + str(mask_fixels) + " fixels"); } else { @@ -182,10 +183,8 @@ void run() { data_header.spacing(0) = data_header.spacing(1) = data_header.spacing(2) = NaN; data_header.stride(0) = 1; data_header.stride(1) = 2; data_header.stride(2) = 3; mask = Image::scratch (data_header, "scratch fixel mask"); - for (uint32_t f = 0; f != num_fixels; ++f) { - mask.index(0) = f; + for (mask.index(0) = 0; mask.index(0) != num_fixels; ++mask.index(0)) mask.value() = true; - } mask_fixels = num_fixels; for (uint32_t f = 0; f != num_fixels; ++f) fixel2row[f] = f; @@ -205,7 +204,7 @@ void run() { Eigen::Vector3 vox ((default_type)index_image.index(0), (default_type)index_image.index(1), (default_type)index_image.index(2)); vox = image_transform.voxel2scanner * vox; index_image.index(3) = 1; - uint32_t offset = index_image.value(); + const index_type offset = index_image.value(); size_t fixel_index = 0; for (auto f = Fixel::Loop (index_image) (directions_data); f; ++f, ++fixel_index) { directions[offset + fixel_index] = directions_data.row(1); @@ -217,7 +216,7 @@ void run() { vector identifiers; Header header; { - ProgressBar progress ("validating input files..."); + ProgressBar progress ("validating input files"); std::ifstream ifs (argument[1].c_str()); std::string temp; while (getline (ifs, temp)) { @@ -268,7 +267,7 @@ void run() { throw Exception ("only a single contrast vector (defined as a row) is currently supported"); // Compute fixel-fixel connectivity - vector > connectivity_matrix (num_fixels); + Stats::CFE::init_connectivity_matrix_type connectivity_matrix (num_fixels); vector fixel_TDI (num_fixels, 0); const std::string track_filename = argument[4]; DWI::Tractography::Properties properties; @@ -294,8 +293,10 @@ void run() { } track_file.close(); - // Normalise connectivity matrix and threshold, pre-compute fixel-fixel weights for smoothing. - vector > smoothing_weights (mask_fixels); + // Normalise connectivity matrix, threshold, and put in a more efficient format + Stats::CFE::norm_connectivity_matrix_type norm_connectivity_matrix (mask_fixels); + // Also pre-compute fixel-fixel weights for smoothing. + Stats::CFE::norm_connectivity_matrix_type smoothing_weights (mask_fixels); bool do_smoothing = false; const float gaussian_const2 = 2.0 * smooth_std_dev * smooth_std_dev; @@ -307,7 +308,7 @@ void run() { { ProgressBar progress ("normalising and thresholding fixel-fixel connectivity matrix", num_fixels); - for (uint32_t fixel = 0; fixel < num_fixels; ++fixel) { + for (index_type fixel = 0; fixel < num_fixels; ++fixel) { mask.index(0) = fixel; const int32_t row = fixel2row[fixel]; @@ -316,7 +317,6 @@ void run() { // Here, the connectivity matrix needs to be modified to reflect the // fact that fixel indices in the template fixel image may not // correspond to rows in the statistical analysis - std::map new_connectivity_matrix_row; connectivity_value_type sum_weights = 0.0; for (auto& it : connectivity_matrix[fixel]) { @@ -332,34 +332,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 connectivity_value_type smoothing_weight = connectivity * gaussian_const1 * std::exp (-std::pow (distance, 2) / gaussian_const2); + const connectivity_value_type smoothing_weight = connectivity * gaussian_const1 * std::exp (-Math::pow2 (distance) / gaussian_const2); if (smoothing_weight >= connectivity_threshold) { - smoothing_weights[row][fixel2row[it.first]] = smoothing_weight; + smoothing_weights[row].push_back (Stats::CFE::NormMatrixElement (fixel2row[it.first], smoothing_weight)); sum_weights += smoothing_weight; } } // Here we pre-exponentiate each connectivity value by C - new_connectivity_matrix_row[fixel2row[it.first]] = std::pow (connectivity, cfe_c); + norm_connectivity_matrix[row].push_back (Stats::CFE::NormMatrixElement (fixel2row[it.first], std::pow (connectivity, cfe_c))); } } // Make sure the fixel is fully connected to itself - new_connectivity_matrix_row[row] = Stats::CFE::connectivity (1.0); - smoothing_weights[row][row] = connectivity_value_type(gaussian_const1); + norm_connectivity_matrix[row].push_back (Stats::CFE::NormMatrixElement (uint32_t(row), connectivity_value_type(1.0))); + smoothing_weights[row].push_back (Stats::CFE::NormMatrixElement (uint32_t(row), connectivity_value_type(gaussian_const1))); sum_weights += connectivity_value_type(gaussian_const1); // Normalise smoothing weights const connectivity_value_type norm_factor = connectivity_value_type(1.0) / sum_weights; for (auto i : smoothing_weights[row]) - i.second *= norm_factor; - - // Write the new connectivity matrix data to the relevant structure - // Note that due to fixel masking, this may not be the same row as - // the fixel index - connectivity_matrix[row] = std::move (new_connectivity_matrix_row); - // Force deallocation of memory used - std::map().swap (new_connectivity_matrix_row); + i.normalise (norm_factor); + + // Force deallocation of memory used for this fixel in the original matrix + std::map().swap (connectivity_matrix[fixel]); + } else { + // If fixel is not in the mask, tract_processor should never assign // any connections to it assert (connectivity_matrix[fixel].empty()); @@ -370,9 +368,11 @@ void run() { } } - // If the connectivity matrix has shrunk in size due to the masking of fixels, - // throw out any now-erroneous data off the end of the vector - connectivity_matrix.resize (mask_fixels); + // The connectivity matrix is now in vector rather than matrix form; + // throw out the structure holding the original data + // (Note however that all entries in the original structure should + // have been deleted during the prior loop) + Stats::CFE::init_connectivity_matrix_type().swap (connectivity_matrix); Header output_header (header); @@ -390,7 +390,7 @@ void run() { matrix_type data (mask_fixels, identifiers.size()); data.setZero(); { - ProgressBar progress ("loading input images", identifiers.size()); + ProgressBar progress (std::string ("loading input images") + (do_smoothing ? " and smoothing" : ""), identifiers.size()); for (size_t subject = 0; subject < identifiers.size(); subject++) { LogLevelLatch log_level (0); @@ -415,7 +415,7 @@ void run() { for (size_t fixel = 0; fixel < mask_fixels; ++fixel) { value_type value = 0.0; for (auto i : smoothing_weights[fixel]) - value += subject_data_vector[i.first] * i.second; + value += subject_data_vector[i.index()] * i.value(); data (fixel, subject) = value; } } else { @@ -425,6 +425,8 @@ void run() { } } + // Free the memory occupied by the data smoothing filter; no longer required + Stats::CFE::norm_connectivity_matrix_type().swap (smoothing_weights); if (!data.allFinite()) throw Exception ("input data contains non-finite value(s)"); @@ -446,7 +448,7 @@ void run() { Math::Stats::GLMTTest glm_ttest (data, design, contrast); std::shared_ptr cfe_integrator; - cfe_integrator.reset (new Stats::CFE::Enhancer (connectivity_matrix, cfe_dh, cfe_e, cfe_h)); + cfe_integrator.reset (new Stats::CFE::Enhancer (norm_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 diff --git a/src/stats/cfe.cpp b/src/stats/cfe.cpp index 1f012fda53..83d21dbef5 100644 --- a/src/stats/cfe.cpp +++ b/src/stats/cfe.cpp @@ -23,11 +23,11 @@ namespace MR - TrackProcessor::TrackProcessor (Image& fixel_indexer, + TrackProcessor::TrackProcessor (Image& fixel_indexer, const vector& fixel_directions, Image& fixel_mask, vector& fixel_TDI, - vector >& connectivity_matrix, + init_connectivity_matrix_type& connectivity_matrix, const value_type angular_threshold) : fixel_indexer (fixel_indexer) , fixel_directions (fixel_directions), @@ -41,21 +41,21 @@ namespace MR bool TrackProcessor::operator() (const SetVoxelDir& in) { // For each voxel tract tangent, assign to a fixel - vector tract_fixel_indices; + 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_fixels = fixel_indexer.value(); + const index_type num_fixels = fixel_indexer.value(); if (num_fixels > 0) { fixel_indexer.index(3) = 1; - uint32_t first_index = fixel_indexer.value(); - uint32_t last_index = first_index + num_fixels; + const index_type first_index = fixel_indexer.value(); + const index_type last_index = first_index + num_fixels; // Note: Streamlines can still be assigned to a fixel that is outside the mask; // however this will not be permitted to contribute to the matrix - uint32_t closest_fixel_index = num_fixels; + index_type closest_fixel_index = num_fixels; value_type largest_dp = 0.0; const direction_type dir (i->get_dir().normalized()); - for (uint32_t j = first_index; j < last_index; ++j) { + for (index_type 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; @@ -92,11 +92,11 @@ namespace MR - Enhancer::Enhancer (const vector >& connectivity_map, + Enhancer::Enhancer (const norm_connectivity_matrix_type& connectivity_matrix, const value_type dh, const value_type E, const value_type H) : - connectivity_map (connectivity_map), + connectivity_matrix (connectivity_matrix), dh (dh), E (E), H (H) { } @@ -107,13 +107,13 @@ 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; + vector::const_iterator connected_fixel; + for (size_t fixel = 0; fixel < connectivity_matrix.size(); ++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; + for (connected_fixel = connectivity_matrix[fixel].begin(); connected_fixel != connectivity_matrix[fixel].end(); ++connected_fixel) + if (stats[connected_fixel->index()] > h) + extent += connected_fixel->value(); enhanced_stats[fixel] += std::pow (extent, E) * std::pow (h, H); } if (enhanced_stats[fixel] > max_enhanced_stat) diff --git a/src/stats/cfe.h b/src/stats/cfe.h index 1130be419e..6bf5ebd99e 100644 --- a/src/stats/cfe.h +++ b/src/stats/cfe.h @@ -17,6 +17,7 @@ #include "image.h" #include "image_helpers.h" +#include "types.h" #include "math/math.h" #include "math/stats/typedefs.h" @@ -30,11 +31,11 @@ namespace MR namespace CFE { + using index_type = uint32_t; using value_type = Math::Stats::value_type; using vector_type = Math::Stats::vector_type; using connectivity_value_type = float; using direction_type = Eigen::Matrix; - using connectivity_vector_type = Eigen::Array; using SetVoxelDir = DWI::Tractography::Mapping::SetVoxelDir; @@ -51,6 +52,30 @@ namespace MR }; + // A class to store fixel index / connectivity value pairs + // only after the connectivity matrix has been thresholded / normalised + class NormMatrixElement + { + public: + NormMatrixElement (const index_type fixel_index, + const connectivity_value_type connectivity_value) : + fixel_index (fixel_index), + connectivity_value (connectivity_value) { } + index_type index() const { return fixel_index; } + connectivity_value_type value() const { return connectivity_value; } + void normalise (const connectivity_value_type norm_factor) { connectivity_value *= norm_factor; } + private: + const index_type fixel_index; + connectivity_value_type connectivity_value; + }; + + + + // Different types are used depending on whether the connectivity matrix + // is in the process of being built, or whether it has been normalised + using init_connectivity_matrix_type = vector>; + using norm_connectivity_matrix_type = vector>; + /** @@ -59,21 +84,21 @@ namespace MR class TrackProcessor { MEMALIGN(TrackProcessor) public: - TrackProcessor (Image& fixel_indexer, + TrackProcessor (Image& fixel_indexer, const vector& fixel_directions, Image& fixel_mask, vector& fixel_TDI, - vector >& connectivity_matrix, + init_connectivity_matrix_type& connectivity_matrix, const value_type angular_threshold); bool operator () (const SetVoxelDir& in); private: - Image fixel_indexer; + Image fixel_indexer; const vector& fixel_directions; Image fixel_mask; vector& fixel_TDI; - vector >& connectivity_matrix; + init_connectivity_matrix_type& connectivity_matrix; const value_type angular_threshold_dp; }; @@ -82,7 +107,7 @@ namespace MR class Enhancer : public Stats::EnhancerBase { MEMALIGN (Enhancer) public: - Enhancer (const vector >& connectivity_map, + Enhancer (const norm_connectivity_matrix_type& connectivity_matrix, const value_type dh, const value_type E, const value_type H); @@ -90,7 +115,7 @@ namespace MR protected: - const vector >& connectivity_map; + const norm_connectivity_matrix_type& connectivity_matrix; const value_type dh, E, H; }; From fe72a9ec7e43996858fae579b5b89f019b20776e Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Fri, 30 Jun 2017 12:13:02 +1000 Subject: [PATCH 039/139] Getting rid of mtbin, avoiding users accidentally still using it.There are important fundamental reasons why the method is not producing the results it was supposed to. mtlognorm is a new method; developed, validated, implemented and debugged from scratch. Have discussed this way of proceeding and replacing mtbin at length with Alan; he fully supports it. The last thing we want to see happen, is people producing very suboptimal FBA studies; which was a major risk with the mtbin in the pipeline. Sorry if people had expected more discussion around this, but it just wasn't happening fast enough, and we're under massive time pressure over here at the moment. --- cmd/mtbin.cpp | 332 +----------------------------- docs/reference/commands/mtbin.rst | 24 +-- 2 files changed, 20 insertions(+), 336 deletions(-) diff --git a/cmd/mtbin.cpp b/cmd/mtbin.cpp index d91b36da06..bba89927ca 100644 --- a/cmd/mtbin.cpp +++ b/cmd/mtbin.cpp @@ -14,358 +14,48 @@ #include "command.h" #include "image.h" -#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" -#include "algo/threaded_copy.h" 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), Rami Tabbara (rami.tabbara@florey.edu.au) and Thijs Dhollander (thijs.dhollander@gmail.com)"; + AUTHOR = ""; SYNOPSIS = "Multi-Tissue Bias field correction and Intensity Normalisation (MTBIN)"; DESCRIPTION - + "This command inputs N number of tissue components " - "(e.g. from multi-tissue CSD), and outputs N corrected tissue components. Intensity normalisation is performed by either " - "determining a common global normalisation factor for all tissue types (default) or by normalising each tissue type independently " - "with a single tissue-specific global scale factor." - - + "The -mask option is mandatory, and is optimally provided with a brain mask, such as the one obtained from dwi2mask earlier in the processing pipeline." - - + "Example usage: mtbin wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif." - - + "The estimated multiplicative bias field is guaranteed to have a mean of 1 over all voxels within the mask."; + + "WARNING: there were some major issues with this method. Please start using the new mtlognorm command instead."; 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(); + + Argument ("input output", "").type_image_in().allow_multiple(); OPTIONS - + Option ("mask", "define the mask to compute the normalisation within. This option is mandatory.").required () + + Option ("mask", "").required () + Argument ("image").type_image_in () - + Option ("value", "specify the value to which the summed tissue compartments will be normalised to " - "(Default: sqrt(1/(4*pi)) = " + str(DEFAULT_NORM_VALUE, 6) + ")") + + Option ("value", "") + Argument ("number").type_float () - + Option ("bias", "output the estimated bias field") + + Option ("bias", "") + Argument ("image").type_image_out () - + Option ("independent", "intensity normalise each tissue type independently") + + Option ("independent", "") - + Option ("maxiter", "set the maximum number of iterations. Default(" + str(DEFAULT_MAXITER_VALUE) + "). " - "It will stop before the max iterations if convergence is detected") + + Option ("maxiter", "") + Argument ("number").type_integer() - + Option ("check", "check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure. However, these regions are still corrected for bias fields based on the other image data.") + + Option ("check", "") + Argument ("image").type_image_out (); } -const int n_basis_vecs (20); - - -FORCE_INLINE Eigen::MatrixXd basis_function (const Eigen::Vector3 pos) { - 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; - 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; - basis(19) = x * y * z; - return basis; -} - -// Currently not used, but keep if we want to make mask argument optional in the future -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); -} - - -FORCE_INLINE void refine_mask (Image& summed, - Image& initial_mask, - Image& refined_mask) { - - for (auto i = Loop (summed, 0, 3) (summed, initial_mask, refined_mask); i; ++i) { - if (std::isfinite((float) summed.value ()) && summed.value () > 0.f && initial_mask.value ()) - refined_mask.value () = true; - else - refined_mask.value () = false; - } -} 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"); - - ProgressBar progress ("performing intensity normalisation and bias field correction..."); - vector > input_images; - vector
output_headers; - vector output_filenames; - - - // Open input images and check for output - for (size_t i = 0; i < argument.size(); i += 2) { - progress++; - 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); - - 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])); - output_filenames.push_back (argument[i + 1]); - } - - // Load the mask - Header header_3D (input_images[0]); - header_3D.ndim() = 3; - auto opt = get_options ("mask"); - - auto orig_mask = Image::open (opt[0][0]); - auto initial_mask = Image::scratch (orig_mask); - auto mask = Image::scratch (orig_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]); i; ++i) - summed.value() += input_images[j].value(); - progress++; - } - - // Refine the initial mask to exclude negative summed tissue components - refine_mask (summed, orig_mask, initial_mask); - - threaded_copy (initial_mask, mask); - - size_t num_voxels = 0; - for (auto i = Loop (mask) (mask); i; ++i) { - if (mask.value()) - num_voxels++; - } - progress++; - if (!num_voxels) - throw Exception ("error in automatic mask generation. Mask contains no voxels"); + throw Exception ("There were some major issues with this method. Please start using the new mtlognorm command instead."); - 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 - 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 = 1; - bool converged = false; - - // Iterate until convergence or max iterations performed - while (!converged && iter < max_iter) { - - INFO ("iteration: " + str(iter)); - // 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++; - - INFO ("scale factors: " + str(scale_factors.transpose())); - - - // Solve for bias field weights - Transform transform (mask); - Eigen::MatrixXd bias_field_basis (num_voxels, n_basis_vecs); - 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++; - bias_field_weights = bias_field_basis.colPivHouseholderQr().solve(y); - 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; - - progress++; - - // Check for convergence - Eigen::MatrixXd diff; - if (iter > 1) { - diff = previous_scale_factors.array() - scale_factors.array(); - diff = diff.array().abs() / previous_scale_factors.array(); - INFO ("percentage change in estimated scale factors: " + str(diff.mean() * 100)); - if (diff.mean() < 0.001) - converged = true; - } - - // Re-evaluate mask - if (!converged) { - 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(); - } - } - - refine_mask (summed, initial_mask, mask); - - 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++; - } - - 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"); - 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 - 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_image.value() = scale_factors(j, 0) * input_images[j].value() / bias_field.value(); - } - } } + diff --git a/docs/reference/commands/mtbin.rst b/docs/reference/commands/mtbin.rst index fbe1ac0b17..42d6c9fe93 100644 --- a/docs/reference/commands/mtbin.rst +++ b/docs/reference/commands/mtbin.rst @@ -15,33 +15,27 @@ Usage 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 +- *input output*: Description ----------- -This command inputs N number of tissue components (e.g. from multi-tissue CSD), and outputs N corrected tissue components. Intensity normalisation is performed by either determining a common global normalisation factor for all tissue types (default) or by normalising each tissue type independently with a single tissue-specific global scale factor. - -The -mask option is mandatory, and is optimally provided with a brain mask, such as the one obtained from dwi2mask earlier in the processing pipeline. - -Example usage: mtbin wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif. - -The estimated multiplicative bias field is guaranteed to have a mean of 1 over all voxels within the mask. +WARNING: there were some major issues with this method. Please start using the new mtlognorm command instead. Options ------- -- **-mask image** define the mask to compute the normalisation within. This option is mandatory. +- **-mask image** -- **-value number** specify the value to which the summed tissue compartments will be normalised to (Default: sqrt(1/(4*pi)) = 0.282094) +- **-value number** -- **-bias image** output the estimated bias field +- **-bias image** -- **-independent** intensity normalise each tissue type independently +- **-independent** -- **-maxiter number** set the maximum number of iterations. Default(100). It will stop before the max iterations if convergence is detected +- **-maxiter number** -- **-check image** check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure. However, these regions are still corrected for bias fields based on the other image data. +- **-check image** Standard options ^^^^^^^^^^^^^^^^ @@ -66,7 +60,7 @@ Standard options -**Author:** David Raffelt (david.raffelt@florey.edu.au), Rami Tabbara (rami.tabbara@florey.edu.au) and Thijs Dhollander (thijs.dhollander@gmail.com) +**Author:** **Copyright:** Copyright (c) 2008-2017 the MRtrix3 contributors. From dc986a1fd2f93bb608aaccc18152d256fa3468db Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Fri, 30 Jun 2017 17:26:53 +1000 Subject: [PATCH 040/139] mtlognorm interface --- cmd/mtlognorm.cpp | 45 ++++++++++++++------------- docs/reference/commands/mtlognorm.rst | 18 +++++------ 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index 58b25a434a..446eb0327c 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -38,36 +38,39 @@ void usage () SYNOPSIS = "Multi-tissue informed log-domain intensity normalisation"; DESCRIPTION - + "This command inputs N number of tissue components " - "(e.g. from multi-tissue CSD), and outputs N corrected tissue components. Intensity normalisation is performed by either " - "determining a common global normalisation factor for all tissue types (default) or by normalising each tissue type independently " - "with a single tissue-specific global scale factor." + + "This command inputs N number of tissue components (e.g. from multi-tissue CSD) " + "and outputs N corrected tissue components. Intensity normalisation is performed " + "in the log-domain, and can smoothly vary spatially to accomodate the (residual) " + "effects of intensity inhomogeneities." - + "The -mask option is mandatory, and is optimally provided with a brain mask, such as the one obtained from dwi2mask earlier in the processing pipeline." + + "The -mask option is mandatory and is optimally provided with a brain mask " + "(such as the one obtained from dwi2mask earlier in the processing pipeline). " + "Outlier areas with exceptionally low or high combined tissue contributions are " + "accounted for and reoptimised as the intensity inhomogeneity estimation becomes " + "more accurate." - + "Example usage: mtlognorm wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif."; + + "Example usage: mtlognorm wmfod.mif wmfod_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif."; 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(); + + Argument ("input output", "list of all input and output tissue compartment files. See example usage in the description.").type_image_in().allow_multiple(); OPTIONS - + Option ("mask", "define the mask to compute the normalisation within. This option is mandatory.").required () + + Option ("mask", "the mask defines the data used to compute the intensity normalisation. This option is mandatory.").required () + Argument ("image").type_image_in () - + Option ("value", "specify the value to which the summed tissue compartments will be normalised to " - "(Default: sqrt(1/(4*pi)) = " + str(DEFAULT_NORM_VALUE, 6) + ")") - + Argument ("number").type_float () + + Option ("niter", "set the number of iterations. (default: " + str(DEFAULT_MAIN_ITER_VALUE) + ")") + + Argument ("number").type_integer() - + Option ("bias", "output the estimated bias field") + + Option ("check_norm", "output the final estimated spatially varying intensity level that is used for normalisation.") + Argument ("image").type_image_out () - + Option ("maxiter", "set the number of iterations. Default(" + str(DEFAULT_MAIN_ITER_VALUE) + ").") - + Argument ("number").type_integer() + + Option ("check_mask", "output the final mask used to compute the normalisation. " + "This mask excludes regions identified as outliers by the optimisation process.") + + Argument ("image").type_image_out () - + Option ("check", "check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure." - "However, these regions are still corrected for bias fields based on the other image data.") - + Argument ("image").type_image_out (); + + Option ("value", "specify the value to which the summed tissue compartments will be normalised. " + "(default: " + str(DEFAULT_NORM_VALUE, 6) + ", SH DC term for unit angular integral)") + + Argument ("number").type_float (); } const int n_basis_vecs (20); @@ -213,7 +216,7 @@ void run () const float log_norm_value = std::log (normalisation_value); - const size_t max_iter = get_option_value ("maxiter", DEFAULT_MAIN_ITER_VALUE); + const size_t max_iter = get_option_value ("niter", DEFAULT_MAIN_ITER_VALUE); const size_t max_inner_iter = DEFAULT_INNER_MAXITER_VALUE; @@ -387,14 +390,14 @@ void run () iter++; } - opt = get_options ("bias"); + opt = get_options ("check_norm"); if (opt.size()) { auto bias_field_output = ImageType::create (opt[0][0], header_3D); threaded_copy (bias_field_image, bias_field_output); } progress++; - opt = get_options ("check"); + opt = get_options ("check_mask"); if (opt.size()) { auto mask_output = ImageType::create (opt[0][0], mask); threaded_copy (mask, mask_output); diff --git a/docs/reference/commands/mtlognorm.rst b/docs/reference/commands/mtlognorm.rst index 4b1d014909..dc9a075c4c 100644 --- a/docs/reference/commands/mtlognorm.rst +++ b/docs/reference/commands/mtlognorm.rst @@ -15,29 +15,29 @@ Usage mtlognorm [ 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 +- *input output*: list of all input and output tissue compartment files. See example usage in the description. Description ----------- -This command inputs N number of tissue components (e.g. from multi-tissue CSD), and outputs N corrected tissue components. Intensity normalisation is performed by either determining a common global normalisation factor for all tissue types (default) or by normalising each tissue type independently with a single tissue-specific global scale factor. +This command inputs N number of tissue components (e.g. from multi-tissue CSD) and outputs N corrected tissue components. Intensity normalisation is performed in the log-domain, and can smoothly vary spatially to accomodate the (residual) effects of intensity inhomogeneities. -The -mask option is mandatory, and is optimally provided with a brain mask, such as the one obtained from dwi2mask earlier in the processing pipeline. +The -mask option is mandatory and is optimally provided with a brain mask (such as the one obtained from dwi2mask earlier in the processing pipeline). Outlier areas with exceptionally low or high combined tissue contributions are accounted for and reoptimised as the intensity inhomogeneity estimation becomes more accurate. -Example usage: mtlognorm wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif. +Example usage: mtlognorm wmfod.mif wmfod_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif. Options ------- -- **-mask image** define the mask to compute the normalisation within. This option is mandatory. +- **-mask image** the mask defines the data used to compute the intensity normalisation. This option is mandatory. -- **-value number** specify the value to which the summed tissue compartments will be normalised to (Default: sqrt(1/(4*pi)) = 0.282095) +- **-niter number** set the number of iterations. (default: 15) -- **-bias image** output the estimated bias field +- **-check_norm image** output the final estimated spatially varying intensity level that is used for normalisation. -- **-maxiter number** set the number of iterations. Default(15). +- **-check_mask image** output the final mask used to compute the normalisation. This mask excludes regions identified as outliers by the optimisation process. -- **-check image** check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure.However, these regions are still corrected for bias fields based on the other image data. +- **-value number** specify the value to which the summed tissue compartments will be normalised. (default: 0.282095, SH DC term for unit angular integral) Standard options ^^^^^^^^^^^^^^^^ From 78cb16849e6616cda7ec96494d13b599df1d933d Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Mon, 3 Jul 2017 14:53:09 +1000 Subject: [PATCH 041/139] cleaning up mtlognorm --- cmd/mtlognorm.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index 446eb0327c..e50f05110e 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -15,10 +15,6 @@ #include "command.h" #include "image.h" #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" #include "algo/threaded_copy.h" From 1854ef0d538b178ff09761563c7b33f5972df166 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Mon, 3 Jul 2017 17:03:16 +1000 Subject: [PATCH 042/139] cleaning up mtlognorm take 2 --- cmd/mtlognorm.cpp | 30 +++++++++++++-------------- docs/reference/commands/mtlognorm.rst | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index e50f05110e..6e676b56cb 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -64,7 +64,7 @@ void usage () "This mask excludes regions identified as outliers by the optimisation process.") + Argument ("image").type_image_out () - + Option ("value", "specify the value to which the summed tissue compartments will be normalised. " + + Option ("value", "specify the reference value to which the summed tissue compartments will be normalised. " "(default: " + str(DEFAULT_NORM_VALUE, 6) + ", SH DC term for unit angular integral)") + Argument ("number").type_float (); } @@ -116,9 +116,9 @@ FORCE_INLINE void refine_mask (Image& summed, 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"); + throw Exception ("The number of arguments must be even, provided as pairs of each input and its corresponding output file."); - ProgressBar progress ("performing intensity normalisation and bias field correction..."); + ProgressBar progress ("performing log-domain intensity normalisation..."); using ImageType = Image; using MaskType = Image; @@ -135,7 +135,7 @@ void run () auto image = ImageType::open (argument[i]); if (image.ndim () > 4) - throw Exception ("input image \"" + image.name() + "\" must contain 4 dimensions or less."); + throw Exception ("input image \"" + image.name() + "\" contains more than 4 dimensions."); // Elevate image dimensions to ensure it is 4-dimensional // e.g. x,y,z -> x,y,z,1 @@ -185,7 +185,7 @@ void run () Header h_combined_tissue (input_images[0]); h_combined_tissue.ndim () = 4; h_combined_tissue.size (3) = n_tissue_types; - auto combined_tissue = ImageType::scratch (h_combined_tissue, "Packed tissue components"); + auto combined_tissue = ImageType::scratch (h_combined_tissue, "Tissue components"); for (size_t i = 0; i < n_tissue_types; ++i) { combined_tissue.index (3) = i; @@ -201,14 +201,14 @@ void run () } if (!num_voxels) - throw Exception ("Error in automatic mask generation. Mask contains no voxels"); + throw Exception ("Mask contains no valid voxels"); // Load global normalisation factor const float normalisation_value = get_option_value ("value", DEFAULT_NORM_VALUE); if (normalisation_value <= 0.f) - throw Exception ("Intensity normalisation value must be strictly positive."); + throw Exception ("Normalisation reference value (-value option) must be strictly positive."); const float log_norm_value = std::log (normalisation_value); @@ -320,15 +320,15 @@ void run () double log_sum = 0.f; for (size_t j = 0; j < n_tissue_types; ++j) { if (scale_factors(j) <= 0.0) - throw Exception ("Non-positive tissue intensity normalisation scale factor was computed." - " Tissue index: " + str(j) + " Scale factor: " + str(scale_factors(j)) + + throw Exception ("Non-positive tissue balance factor was computed." + " Tissue index: " + str(j+1) + " Balance factor: " + str(scale_factors(j)) + " Needs to be strictly positive!"); log_sum += std::log (scale_factors(j)); } scale_factors /= std::exp (log_sum / n_tissue_types); } - INFO ("scale factors: " + str(scale_factors.transpose())); + INFO ("Balance factors: " + str(scale_factors.transpose())); // Perform outlier rejection on log-domain of summed images outlier_rejection(1.5f); @@ -348,7 +348,7 @@ void run () } - // Solve for bias field weights in the log domain + // Solve for normalisation field weights in the log domain Transform transform (mask); Eigen::MatrixXd bias_field_basis (num_voxels, n_basis_vecs); Eigen::MatrixXd X (num_voxels, n_tissue_types); @@ -371,14 +371,14 @@ void run () bias_field_weights = bias_field_basis.colPivHouseholderQr().solve(y); - // Generate bias field in the log domain + // Generate normalisation field in the log domain for (auto i = Loop (0, 3) (bias_field_log); i; ++i) { Eigen::Vector3 vox (bias_field_log.index(0), bias_field_log.index(1), bias_field_log.index(2)); Eigen::Vector3 pos = transform.voxel2scanner * vox; bias_field_log.value() = basis_function (pos).col(0).dot (bias_field_weights.col(0)); } - // Generate bias field in the image domain + // Generate normalisation field in the image domain for (auto i = Loop (0, 3) (bias_field_log, bias_field_image); i; ++i) bias_field_image.value () = std::exp(bias_field_log.value()); @@ -401,7 +401,7 @@ void run () progress++; - // Output bias corrected and normalised tissue maps + // Generate output headers uint32_t total_count = 0; for (size_t i = 0; i < output_headers.size(); ++i) { uint32_t count = 1; @@ -410,7 +410,7 @@ void run () total_count += count; } - // Compute log-norm scale + // Compute log-norm scale parameter (geometric mean of normalisation field in outlier-free mask). float lognorm_scale { 0.f }; if (num_voxels) { for (auto i = Loop (0,3) (mask, bias_field_log); i; ++i) { diff --git a/docs/reference/commands/mtlognorm.rst b/docs/reference/commands/mtlognorm.rst index dc9a075c4c..3ef855e30c 100644 --- a/docs/reference/commands/mtlognorm.rst +++ b/docs/reference/commands/mtlognorm.rst @@ -37,7 +37,7 @@ Options - **-check_mask image** output the final mask used to compute the normalisation. This mask excludes regions identified as outliers by the optimisation process. -- **-value number** specify the value to which the summed tissue compartments will be normalised. (default: 0.282095, SH DC term for unit angular integral) +- **-value number** specify the reference value to which the summed tissue compartments will be normalised. (default: 0.282095, SH DC term for unit angular integral) Standard options ^^^^^^^^^^^^^^^^ From 2ce23d2df7b0714adb7d5d1fbe1ee128c7e50eac Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 4 Jul 2017 11:38:16 +1000 Subject: [PATCH 043/139] cleaning up mtlognorm take 3 --- cmd/mtlognorm.cpp | 69 ++++++++++++++------------- docs/reference/commands/mtlognorm.rst | 2 +- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index 6e676b56cb..e27951ed83 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -34,8 +34,8 @@ void usage () SYNOPSIS = "Multi-tissue informed log-domain intensity normalisation"; DESCRIPTION - + "This command inputs N number of tissue components (e.g. from multi-tissue CSD) " - "and outputs N corrected tissue components. Intensity normalisation is performed " + + "This command inputs any number of tissue components (e.g. from multi-tissue CSD) " + "and outputs corresponding normalised tissue components. Intensity normalisation is performed " "in the log-domain, and can smoothly vary spatially to accomodate the (residual) " "effects of intensity inhomogeneities." @@ -100,6 +100,7 @@ FORCE_INLINE Eigen::MatrixXd basis_function (const Eigen::Vector3 pos) { return basis; } +// Removes non-physical voxels from the mask FORCE_INLINE void refine_mask (Image& summed, Image& initial_mask, Image& refined_mask) { @@ -135,7 +136,7 @@ void run () auto image = ImageType::open (argument[i]); if (image.ndim () > 4) - throw Exception ("input image \"" + image.name() + "\" contains more than 4 dimensions."); + throw Exception ("Input image \"" + image.name() + "\" contains more than 4 dimensions."); // Elevate image dimensions to ensure it is 4-dimensional // e.g. x,y,z -> x,y,z,1 @@ -150,7 +151,7 @@ void run () check_dimensions (input_images[0], input_images[i / 2], 0, 3); if (Path::exists (argument[i + 1]) && !App::overwrite_files) - throw Exception ("output file \"" + argument[i] + "\" already exists (use -force option to force overwrite)"); + throw Exception ("Output file \"" + argument[i] + "\" already exists. (use -force option to force overwrite)"); output_headers.emplace_back (h_image4d); output_filenames.emplace_back (argument[i + 1]); @@ -201,7 +202,7 @@ void run () } if (!num_voxels) - throw Exception ("Mask contains no valid voxels"); + throw Exception ("Mask contains no valid voxels."); // Load global normalisation factor @@ -216,15 +217,15 @@ void run () const size_t max_inner_iter = DEFAULT_INNER_MAXITER_VALUE; - // Initialise bias fields in both image and log domain - Eigen::MatrixXd bias_field_weights (n_basis_vecs, 0); + // Initialise normalisation fields in both image and log domain + Eigen::MatrixXd norm_field_weights (n_basis_vecs, 0); - auto bias_field_image = ImageType::scratch (header_3D); - auto bias_field_log = ImageType::scratch (header_3D); + auto norm_field_image = ImageType::scratch (header_3D); + auto norm_field_log = ImageType::scratch (header_3D); - for (auto i = Loop(bias_field_log) (bias_field_image, bias_field_log); i; ++i) { - bias_field_image.value() = 1.f; - bias_field_log.value() = 0.f; + for (auto i = Loop(norm_field_log) (norm_field_image, norm_field_log); i; ++i) { + norm_field_image.value() = 1.f; + norm_field_log.value() = 0.f; } Eigen::VectorXd scale_factors (n_tissue_types); @@ -240,9 +241,9 @@ void run () auto summed_log = ImageType::scratch (header_3D); for (size_t j = 0; j < n_tissue_types; ++j) { - for (auto i = Loop (0, 3) (summed_log, combined_tissue, bias_field_image); i; ++i) { + for (auto i = Loop (0, 3) (summed_log, combined_tissue, norm_field_image); i; ++i) { combined_tissue.index(3) = j; - summed_log.value() += scale_factors(j) * combined_tissue.value() / bias_field_image.value(); + summed_log.value() += scale_factors(j) * combined_tissue.value() / norm_field_image.value(); } summed_log.value() = std::log(summed_log.value()); @@ -290,9 +291,9 @@ void run () // Iteratively compute intensity normalisation scale factors // with outlier rejection size_t norm_iter = 1; - bool norm_converged = false; + bool balance_converged = false; - while (!norm_converged && norm_iter <= max_inner_iter) { + while (!balance_converged && norm_iter <= max_inner_iter) { INFO ("norm iteration: " + str(norm_iter)); @@ -304,11 +305,11 @@ void run () y.fill (1); uint32_t index = 0; - for (auto i = Loop (0, 3) (mask, combined_tissue, bias_field_image); i; ++i) { + for (auto i = Loop (0, 3) (mask, combined_tissue, norm_field_image); i; ++i) { if (mask.value()) { for (size_t j = 0; j < n_tissue_types; ++j) { combined_tissue.index (3) = j; - X (index, j) = combined_tissue.value() / bias_field_image.value(); + X (index, j) = combined_tissue.value() / norm_field_image.value(); } ++index; } @@ -334,10 +335,10 @@ void run () outlier_rejection(1.5f); // Check for convergence - norm_converged = true; + balance_converged = true; for (auto i = Loop (0, 3) (mask, prev_mask); i; ++i) { if (mask.value() != prev_mask.value()) { - norm_converged = false; + balance_converged = false; break; } } @@ -350,7 +351,7 @@ void run () // Solve for normalisation field weights in the log domain Transform transform (mask); - Eigen::MatrixXd bias_field_basis (num_voxels, n_basis_vecs); + Eigen::MatrixXd norm_field_basis (num_voxels, n_basis_vecs); Eigen::MatrixXd X (num_voxels, n_tissue_types); Eigen::VectorXd y (num_voxels); uint32_t index = 0; @@ -358,7 +359,7 @@ void run () 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); + norm_field_basis.row (index) = basis_function (pos).col(0); double sum = 0.0; for (size_t j = 0; j < n_tissue_types; ++j) { @@ -369,18 +370,18 @@ void run () } } - bias_field_weights = bias_field_basis.colPivHouseholderQr().solve(y); + norm_field_weights = norm_field_basis.colPivHouseholderQr().solve(y); // Generate normalisation field in the log domain - for (auto i = Loop (0, 3) (bias_field_log); i; ++i) { - Eigen::Vector3 vox (bias_field_log.index(0), bias_field_log.index(1), bias_field_log.index(2)); + for (auto i = Loop (0, 3) (norm_field_log); i; ++i) { + Eigen::Vector3 vox (norm_field_log.index(0), norm_field_log.index(1), norm_field_log.index(2)); Eigen::Vector3 pos = transform.voxel2scanner * vox; - bias_field_log.value() = basis_function (pos).col(0).dot (bias_field_weights.col(0)); + norm_field_log.value() = basis_function (pos).col(0).dot (norm_field_weights.col(0)); } // Generate normalisation field in the image domain - for (auto i = Loop (0, 3) (bias_field_log, bias_field_image); i; ++i) - bias_field_image.value () = std::exp(bias_field_log.value()); + for (auto i = Loop (0, 3) (norm_field_log, norm_field_image); i; ++i) + norm_field_image.value () = std::exp(norm_field_log.value()); progress++; iter++; @@ -388,8 +389,8 @@ void run () opt = get_options ("check_norm"); if (opt.size()) { - auto bias_field_output = ImageType::create (opt[0][0], header_3D); - threaded_copy (bias_field_image, bias_field_output); + auto norm_field_output = ImageType::create (opt[0][0], header_3D); + threaded_copy (norm_field_image, norm_field_output); } progress++; @@ -413,9 +414,9 @@ void run () // Compute log-norm scale parameter (geometric mean of normalisation field in outlier-free mask). float lognorm_scale { 0.f }; if (num_voxels) { - for (auto i = Loop (0,3) (mask, bias_field_log); i; ++i) { + for (auto i = Loop (0,3) (mask, norm_field_log); i; ++i) { if (mask.value ()) - lognorm_scale += bias_field_log.value (); + lognorm_scale += norm_field_log.value (); } lognorm_scale = std::exp(lognorm_scale / (float)num_voxels); @@ -428,13 +429,13 @@ void run () const size_t n_vols = input_images[j].size(3); const Eigen::VectorXf zero_vec = Eigen::VectorXf::Zero (n_vols); - for (auto i = Loop (0,3) (output_image, input_images[j], bias_field_image); i; ++i) { + for (auto i = Loop (0,3) (output_image, input_images[j], norm_field_image); i; ++i) { input_images[j].index(3) = 0; if (input_images[j].value() < 0.f) output_image.row(3) = zero_vec; else - output_image.row(3) = Eigen::VectorXf{input_images[j].row(3)} / bias_field_image.value(); + output_image.row(3) = Eigen::VectorXf{input_images[j].row(3)} / norm_field_image.value(); } } } diff --git a/docs/reference/commands/mtlognorm.rst b/docs/reference/commands/mtlognorm.rst index 3ef855e30c..82f3312455 100644 --- a/docs/reference/commands/mtlognorm.rst +++ b/docs/reference/commands/mtlognorm.rst @@ -20,7 +20,7 @@ Usage Description ----------- -This command inputs N number of tissue components (e.g. from multi-tissue CSD) and outputs N corrected tissue components. Intensity normalisation is performed in the log-domain, and can smoothly vary spatially to accomodate the (residual) effects of intensity inhomogeneities. +This command inputs any number of tissue components (e.g. from multi-tissue CSD) and outputs corresponding normalised tissue components. Intensity normalisation is performed in the log-domain, and can smoothly vary spatially to accomodate the (residual) effects of intensity inhomogeneities. The -mask option is mandatory and is optimally provided with a brain mask (such as the one obtained from dwi2mask earlier in the processing pipeline). Outlier areas with exceptionally low or high combined tissue contributions are accounted for and reoptimised as the intensity inhomogeneity estimation becomes more accurate. From 3510a10405187f2272285642e4dd0faac11c3202 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 4 Jul 2017 11:56:19 +1000 Subject: [PATCH 044/139] cleaning up mtlognorm take 4 --- cmd/mtbin.cpp | 2 +- cmd/mtlognorm.cpp | 34 +++++++++++++-------------- docs/reference/commands/mtbin.rst | 2 +- docs/reference/commands/mtlognorm.rst | 2 +- docs/reference/commands_list.rst | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/cmd/mtbin.cpp b/cmd/mtbin.cpp index bba89927ca..4aaae17425 100644 --- a/cmd/mtbin.cpp +++ b/cmd/mtbin.cpp @@ -23,7 +23,7 @@ void usage () { AUTHOR = ""; - SYNOPSIS = "Multi-Tissue Bias field correction and Intensity Normalisation (MTBIN)"; + SYNOPSIS = "Please use the new mtlognorm command instead."; DESCRIPTION + "WARNING: there were some major issues with this method. Please start using the new mtlognorm command instead."; diff --git a/cmd/mtlognorm.cpp b/cmd/mtlognorm.cpp index e27951ed83..d0aee0c704 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtlognorm.cpp @@ -35,9 +35,9 @@ void usage () DESCRIPTION + "This command inputs any number of tissue components (e.g. from multi-tissue CSD) " - "and outputs corresponding normalised tissue components. Intensity normalisation is performed " - "in the log-domain, and can smoothly vary spatially to accomodate the (residual) " - "effects of intensity inhomogeneities." + "and outputs corresponding normalised tissue components. Intensity normalisation is " + "performed in the log-domain, and can smoothly vary spatially to accomodate the " + "effects of (residual) intensity inhomogeneities." + "The -mask option is mandatory and is optimally provided with a brain mask " "(such as the one obtained from dwi2mask earlier in the processing pipeline). " @@ -228,22 +228,22 @@ void run () norm_field_log.value() = 0.f; } - Eigen::VectorXd scale_factors (n_tissue_types); - scale_factors.fill(1); + Eigen::VectorXd balance_factors (n_tissue_types); + balance_factors.fill(1); size_t iter = 1; // Store lambda-function for performing outlier-rejection. // We perform a coarse outlier-rejection initially as well as // a finer outlier-rejection within the inner loop when computing - // normalisation scale factors + // tissue balance factors auto outlier_rejection = [&](float outlier_range) { auto summed_log = ImageType::scratch (header_3D); for (size_t j = 0; j < n_tissue_types; ++j) { for (auto i = Loop (0, 3) (summed_log, combined_tissue, norm_field_image); i; ++i) { combined_tissue.index(3) = j; - summed_log.value() += scale_factors(j) * combined_tissue.value() / norm_field_image.value(); + summed_log.value() += balance_factors(j) * combined_tissue.value() / norm_field_image.value(); } summed_log.value() = std::log(summed_log.value()); @@ -288,7 +288,7 @@ void run () INFO ("iteration: " + str(iter)); - // Iteratively compute intensity normalisation scale factors + // Iteratively compute tissue balance factors // with outlier rejection size_t norm_iter = 1; bool balance_converged = false; @@ -299,7 +299,7 @@ void run () if (n_tissue_types > 1) { - // Solve for tissue normalisation scale factors + // Solve for tissue balance factors Eigen::MatrixXd X (num_voxels, n_tissue_types); Eigen::VectorXd y (num_voxels); y.fill (1); @@ -315,21 +315,21 @@ void run () } } - scale_factors = X.colPivHouseholderQr().solve(y); + balance_factors = X.colPivHouseholderQr().solve(y); - // Ensure our scale factors satisfy the condition that sum(log(scale_factors)) = 0 + // Ensure our balance factors satisfy the condition that sum(log(balance_factors)) = 0 double log_sum = 0.f; for (size_t j = 0; j < n_tissue_types; ++j) { - if (scale_factors(j) <= 0.0) + if (balance_factors(j) <= 0.0) throw Exception ("Non-positive tissue balance factor was computed." - " Tissue index: " + str(j+1) + " Balance factor: " + str(scale_factors(j)) + + " Tissue index: " + str(j+1) + " Balance factor: " + str(balance_factors(j)) + " Needs to be strictly positive!"); - log_sum += std::log (scale_factors(j)); + log_sum += std::log (balance_factors(j)); } - scale_factors /= std::exp (log_sum / n_tissue_types); + balance_factors /= std::exp (log_sum / n_tissue_types); } - INFO ("Balance factors: " + str(scale_factors.transpose())); + INFO ("Balance factors: " + str(balance_factors.transpose())); // Perform outlier rejection on log-domain of summed images outlier_rejection(1.5f); @@ -364,7 +364,7 @@ void run () double sum = 0.0; for (size_t j = 0; j < n_tissue_types; ++j) { combined_tissue.index(3) = j; - sum += scale_factors(j) * combined_tissue.value() ; + sum += balance_factors(j) * combined_tissue.value() ; } y (index++) = std::log(sum) - log_norm_value; } diff --git a/docs/reference/commands/mtbin.rst b/docs/reference/commands/mtbin.rst index 42d6c9fe93..899305ce12 100644 --- a/docs/reference/commands/mtbin.rst +++ b/docs/reference/commands/mtbin.rst @@ -6,7 +6,7 @@ mtbin Synopsis -------- -Multi-Tissue Bias field correction and Intensity Normalisation (MTBIN) +Please use the new mtlognorm command instead. Usage -------- diff --git a/docs/reference/commands/mtlognorm.rst b/docs/reference/commands/mtlognorm.rst index 82f3312455..cb7e8a19d9 100644 --- a/docs/reference/commands/mtlognorm.rst +++ b/docs/reference/commands/mtlognorm.rst @@ -20,7 +20,7 @@ Usage Description ----------- -This command inputs any number of tissue components (e.g. from multi-tissue CSD) and outputs corresponding normalised tissue components. Intensity normalisation is performed in the log-domain, and can smoothly vary spatially to accomodate the (residual) effects of intensity inhomogeneities. +This command inputs any number of tissue components (e.g. from multi-tissue CSD) and outputs corresponding normalised tissue components. Intensity normalisation is performed in the log-domain, and can smoothly vary spatially to accomodate the effects of (residual) intensity inhomogeneities. The -mask option is mandatory and is optimally provided with a brain mask (such as the one obtained from dwi2mask earlier in the processing pipeline). Outlier areas with exceptionally low or high combined tissue contributions are accounted for and reoptimised as the intensity inhomogeneity estimation becomes more accurate. diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index e7f293f775..d17e5dfe83 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -188,7 +188,7 @@ List of MRtrix3 commands :ref:`mrthreshold`, "Create bitwise image by thresholding image intensity" :ref:`mrtransform`, "Apply spatial transformations to an image" :ref:`mrview`, "The MRtrix image viewer." - :ref:`mtbin`, "Multi-Tissue Bias field correction and Intensity Normalisation (MTBIN)" + :ref:`mtbin`, "Please use the new mtlognorm command instead." :ref:`mtlognorm`, "Multi-tissue informed log-domain intensity normalisation" :ref:`mtnormalise`, "Multi-tissue normalise" :ref:`peaks2amp`, "Convert peak directions image to amplitudes" From 3769f48b80898495245feab934db9c004f35bcf1 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 4 Jul 2017 12:15:51 +1000 Subject: [PATCH 045/139] fixelcfestats: Make sure connectivity matrix access functions are inline --- lib/mrtrix3/app.py | 4 +--- src/stats/cfe.h | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/mrtrix3/app.py b/lib/mrtrix3/app.py index ae1306e8c8..d79964beb6 100644 --- a/lib/mrtrix3/app.py +++ b/lib/mrtrix3/app.py @@ -7,13 +7,11 @@ # - cmdline.addCitation(), cmdline.addDescription(), cmdline.setCopyright() as needed # - Add arguments and options to 'cmdline' as needed # - parse() -# - checkOutputFile() as needed +# - checkOutputPath() as needed # - makeTempDir() if the script requires a temporary directory # - gotoTempDir() if the script is using a temporary directory # - complete() # -# checkOutputFile() can be called at any time after parse() to check if an intended output -# file already exists diff --git a/src/stats/cfe.h b/src/stats/cfe.h index 6bf5ebd99e..f6029d1a89 100644 --- a/src/stats/cfe.h +++ b/src/stats/cfe.h @@ -61,9 +61,9 @@ namespace MR const connectivity_value_type connectivity_value) : fixel_index (fixel_index), connectivity_value (connectivity_value) { } - index_type index() const { return fixel_index; } - connectivity_value_type value() const { return connectivity_value; } - void normalise (const connectivity_value_type norm_factor) { connectivity_value *= norm_factor; } + FORCE_INLINE index_type index() const { return fixel_index; } + FORCE_INLINE connectivity_value_type value() const { return connectivity_value; } + FORCE_INLINE void normalise (const connectivity_value_type norm_factor) { connectivity_value *= norm_factor; } private: const index_type fixel_index; connectivity_value_type connectivity_value; From ce49bea16094539e7a5ab12f8f6a78a5856e3a4d Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 4 Jul 2017 12:45:08 +1000 Subject: [PATCH 046/139] update FBA docs take 1 --- .../common_fba_steps/population_template.rst | 2 +- .../common_fba_steps/population_template2.rst | 4 +-- .../common_fba_steps/tractography.rst | 2 +- .../mt_fibre_density_cross-section.rst | 28 +++++++++---------- .../ss_fibre_density_cross-section.rst | 20 ++++++------- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/fixel_based_analysis/common_fba_steps/population_template.rst b/docs/fixel_based_analysis/common_fba_steps/population_template.rst index 9f5383db76..5144c0ed99 100644 --- a/docs/fixel_based_analysis/common_fba_steps/population_template.rst +++ b/docs/fixel_based_analysis/common_fba_steps/population_template.rst @@ -1,4 +1,4 @@ -Population template creation is the most time consuming step in a fixel-based analysis. If you have a large number of subjects in your study, we recommend building the template from a subset of 20-40 individuals. Subjects should be chosen to ensure the generated template is representative of your population (i.e. equal number of patients and controls). To build a template, place all FOD images in a single folder. We also recommend placing a set of corresponding mask images (with the same prefix as the FOD images) in another folder. Using masks can speed up registration significantly:: +Population template creation is a very time consuming step in a fixel-based analysis. If you have a very large number of subjects in your study, we recommend building the template from a limited subset of 30-40 individuals. Subjects should be chosen to ensure the generated template is representative of your population (e.g. similar number of patients and controls). To build a template, place all FOD images in a single folder. We also highly recommend placing a set of corresponding mask images (with the same prefix as the FOD images) in another folder. Using masks can speed up registration significantly as well as result in a much more accurate template in specific scenarios:: mkdir -p ../template/fod_input mkdir ../template/mask_input diff --git a/docs/fixel_based_analysis/common_fba_steps/population_template2.rst b/docs/fixel_based_analysis/common_fba_steps/population_template2.rst index 3a86eea0cb..45a09279aa 100644 --- a/docs/fixel_based_analysis/common_fba_steps/population_template2.rst +++ b/docs/fixel_based_analysis/common_fba_steps/population_template2.rst @@ -2,7 +2,7 @@ Run the template building script as follows: .. code-block:: console - $ population_template ../template/fod_input -mask_dir ../template/mask_input ../template/fod_template.mif + $ population_template ../template/fod_input -mask_dir ../template/mask_input ../template/wmfod_template.mif **If you are building a template from your entire study population**, run the population_template script use the :code:`-warp_dir warps` option to output a @@ -15,4 +15,4 @@ conventional 4D deformation field format ready for the subsequent steps, run .. code-block:: console - $ foreach ../template/warps/* : warpconvert -type warpfull2deformation -template ../template/fod_template.mif IN PRE/subject2template_warp.mif + $ foreach ../template/warps/* : warpconvert -type warpfull2deformation -template ../template/wmfod_template.mif IN PRE/subject2template_warp.mif diff --git a/docs/fixel_based_analysis/common_fba_steps/tractography.rst b/docs/fixel_based_analysis/common_fba_steps/tractography.rst index 5dfee1a065..2d34b4ab47 100644 --- a/docs/fixel_based_analysis/common_fba_steps/tractography.rst +++ b/docs/fixel_based_analysis/common_fba_steps/tractography.rst @@ -1,4 +1,4 @@ Statistical analysis using `connectivity-based fixel enhancement `_ exploits connectivity information derived from probabilistic fibre tractography. To generate a whole-brain tractogram from the FOD template. Note the remaining steps from here on are executed from the template directory:: cd ../template - tckgen -angle 22.5 -maxlen 250 -minlen 10 -power 1.0 fod_template.mif -seed_image voxel_mask.mif -mask voxel_mask.mif -select 20000000 tracks_20_million.tck + tckgen -angle 22.5 -maxlen 250 -minlen 10 -power 1.0 wmfod_template.mif -seed_image voxel_mask.mif -mask voxel_mask.mif -select 20000000 tracks_20_million.tck diff --git a/docs/fixel_based_analysis/mt_fibre_density_cross-section.rst b/docs/fixel_based_analysis/mt_fibre_density_cross-section.rst index dbcae71d1a..03fb443705 100644 --- a/docs/fixel_based_analysis/mt_fibre_density_cross-section.rst +++ b/docs/fixel_based_analysis/mt_fibre_density_cross-section.rst @@ -10,9 +10,9 @@ This tutorial explains how to perform `fixel-based analysis of fibre density and All steps in this tutorial have written as if the commands are being **run on a cohort of images**, and make extensive use of the :ref:`foreach script to simplify batch processing `. This tutorial also assumes that the imaging dataset is organised with one directory identifying the subject, and all files within identifying the image type. For example:: study/subjects/001_patient/dwi.mif - study/subjects/001_patient/fod.mif + study/subjects/001_patient/wmfod.mif study/subjects/002_control/dwi.mif - study/subjects/002_control/fod.mif + study/subjects/002_control/wmfod.mif .. NOTE:: All commands in this tutorial are run **from the subjects path** up until step 18, where we change directory to the template path @@ -69,16 +69,16 @@ Depending on your data, you may find that computing masks on native resolution D ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When performing analysis of AFD, Constrained Spherical Deconvolution (CSD) should be performed using the group average response functions computed at step 3:: - foreach * : dwi2fod msmt_csd IN/dwi_denoised_preproc_upsampled.mif ../group_average_response_wm.txt IN/fod.mif ../group_average_response_gm.txt IN/gm.mif ../group_average_response_csf.txt IN/csf.mif -mask IN/dwi_mask_upsampled.mif + foreach * : dwi2fod msmt_csd IN/dwi_denoised_preproc_upsampled.mif ../group_average_response_wm.txt IN/wmfod.mif ../group_average_response_gm.txt IN/gm.mif ../group_average_response_csf.txt IN/csf.mif -mask IN/dwi_mask_upsampled.mif 7. Perform simultaneous bias field correction and intensity normalisation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This step performs :ref:`global intensity normalisation ` by scaling all tissue types based on a single scale factor. A single multiplicative bias field is also estimated and applied to correct the output:: +This step performs :ref:`global intensity normalisation ` by scaling all tissue types with a spatially smoothly varying normalisation field:: - foreach * : mtbin IN/fod.mif IN/fod_bias_norm.mif IN/gm.mif IN/gm_bias_norm.mif IN/csf.mif IN/csf_bias_norm.mif -mask IN/dwi_mask_upsampled.mif + foreach * : mtlognorm IN/wmfod.mif IN/wmfod_norm.mif IN/gm.mif IN/gm_norm.mif IN/csf.mif IN/csf_norm.mif -mask IN/dwi_mask_upsampled.mif -.. WARNING:: We strongly recommend you that you check the scale factors applied during intensity normalisation are not influenced by the variable of interest in your study. For example if one group contains global changes in white matter T2 then this may directly influence the intensity normalisation and therefore bias downstream AFD analysis. To check this we recommend you perform an equivalence test to ensure mean scale factors are the same between groups. To output the scale factor applied for all subjects use :code:`foreach * : mrinfo IN/fod_bias_norm.mif -property normalisation_scale_factor`. +.. WARNING:: We recommend you that you check that the normalisation scale (computed during intensity normalisation) is not influenced by the variable of interest in your study. For example if one group contains global (widespread) changes in white matter T2, then this may directly influence the intensity normalisation and therefore bias downstream analysis of apparent fibre density (FD). To check this, you can perform an equivalence test to ensure the overall normalisation scale does not differ between groups. To output these overall normalisation scales for all subjects use :code:`mrinfo */wmfod_norm.mif -property lognorm_scale`. 8. Generate a study-specific unbiased FOD template @@ -88,13 +88,13 @@ This step performs :ref:`global intensity normalisation `__ for more information about a analysis fixel mask). Note that the fixel image output from this step is stored using the :ref:`fixel_format`, which exploits the filesystem to store all fixel data in a directory:: - fod2fixel -mask ../template/voxel_mask.mif -fmls_peak_value 0.2 ../template/fod_template.mif ../template/fixel_mask + fod2fixel -mask ../template/voxel_mask.mif -fmls_peak_value 0.1 ../template/wmfod_template.mif ../template/fixel_mask You can visualise the output fixels using the fixel plot tool from :ref:`mrview`, and opening either the :code:`index.mif` or :code:`directions.mif` found in :code:`../template/fixel_mask`. The automatic thresholding step used above should give you a mask that nicely covers all of white matter, however if not you can always try manually adjusting the threshold with the :code:`mrthreshold -abs` option. -.. NOTE:: We recommend having no more than 500,000 fixels in the analysis_fixel_mask (you can check this by :code:`mrinfo -size ../template/fixel_mask/directions.mif`, and looking at the size of the image along the 1st dimension), otherwise downstream statistical analysis (using :ref:`fixelcfestats`) will run out of RAM). A mask with 500,000 fixels will require a PC with 128GB of RAM for the statistical analysis step. To reduce the number of fixels, try either reducing the number of voxels in the voxel mask by applying a manual threshold using :code:`-abs`, increasing the :code:`-fmls_peak_value`, or reducing the extent of upsampling in step 4. +.. NOTE:: We recommend having no more than 500,000 fixels in the analysis_fixel_mask (you can check this by :code:`mrinfo -size ../template/fixel_mask/directions.mif`, and looking at the size of the image along the 1st dimension), otherwise downstream statistical analysis (using :ref:`fixelcfestats`) may run out of RAM). A mask with 500,000 fixels will require a PC with 128GB of RAM for the statistical analysis step. To reduce the number of fixels, try either reducing the number of voxels in the voxel mask by applying a manual threshold using :code:`-abs`, increasing the :code:`-fmls_peak_value`, or reducing the extent of upsampling in step 4. 12. Warp FOD images to template space ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Note that here we warp FOD images into template space *without* FOD reorientation. Reorientation will be performed in a separate subsequent step:: - foreach * : mrtransform IN/fod_bias_norm.mif -warp IN/subject2template_warp.mif -noreorientation IN/fod_in_template_space.mif + foreach * : mrtransform IN/wmfod_norm.mif -warp IN/subject2template_warp.mif -noreorientation IN/fod_in_template_space.mif 13. Segment FOD images to estimate fixels and their apparent fibre density (FD) diff --git a/docs/fixel_based_analysis/ss_fibre_density_cross-section.rst b/docs/fixel_based_analysis/ss_fibre_density_cross-section.rst index fbb331192e..ebd19e6a0d 100644 --- a/docs/fixel_based_analysis/ss_fibre_density_cross-section.rst +++ b/docs/fixel_based_analysis/ss_fibre_density_cross-section.rst @@ -10,9 +10,9 @@ This tutorial explains how to perform `fixel-based analysis of fibre density and All steps in this tutorial have written as if the commands are being **run on a cohort of images**, and make extensive use of the :ref:`foreach script to simplify batch processing `. This tutorial also assumes that the imaging dataset is organised with one directory identifying the subject, and all files within identifying the image type. For example:: study/subjects/001_patient/dwi.mif - study/subjects/001_patient/fod.mif + study/subjects/001_patient/wmfod.mif study/subjects/002_control/dwi.mif - study/subjects/002_control/fod.mif + study/subjects/002_control/wmfod.mif .. NOTE:: All commands in this tutorial are run **from the subjects path** up until step 20, where we change directory to the template path @@ -117,7 +117,7 @@ Depending on your data, you may find that upsampling the low-resolution masks fr ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When performing analysis of AFD, Constrained Spherical Deconvolution (CSD) should be performed using the group average response function computed at step . If not using AFD in the fixel-based analysis (and therefore you have skipped steps 4-6), however you still want to compute FODs for image registration, then you can use a subject-specific response function. Note that :code:`dwi2fod csd` can be used, however here we use :code:`dwi2fod msmt_csd` (even with single shell data) to benefit from the hard non-negativity constraint:: - foreach * : dwiextract IN/dwi_denoised_preproc_bias_norm_upsampled.mif - \| dwi2fod msmt_csd - ../group_average_response.txt IN/fod.mif -mask IN/dwi_mask_upsampled.mif + foreach * : dwiextract IN/dwi_denoised_preproc_bias_norm_upsampled.mif - \| dwi2fod msmt_csd - ../group_average_response.txt IN/wmfod.mif -mask IN/dwi_mask_upsampled.mif 10. Generate a study-specific unbiased FOD template ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -126,13 +126,13 @@ When performing analysis of AFD, Constrained Spherical Deconvolution (CSD) shoul Symbolic link all FOD images (and masks) into a single input folder. If you have fewer than 40 subjects in your study, you can use the entire population to build the template:: - foreach * : ln -sr IN/fod.mif ../template/fod_input/PRE.mif + foreach * : ln -sr IN/wmfod.mif ../template/fod_input/PRE.mif foreach * : ln -sr IN/dwi_mask_upsampled.mif ../template/mask_input/PRE.mif Alternatively, if you have more than 40 subjects you can randomly select a subset of the individuals. If your study has multiple groups, then ideally you want to select the same number of subjects from each group to ensure the template is un-biased. Assuming the subject directory labels can be used to identify members of each group, you could use:: - foreach `ls -d *patient | sort -R | tail -20` : ln -sr IN/fod.mif ../template/fod_input/PRE.mif ";" ln -sr IN/dwi_mask_upsampled.mif ../template/mask_input/PRE.mif - foreach `ls -d *control | sort -R | tail -20` : ln -sr IN/fod.mif ../template/fod_input/PRE.mif ";" ln -sr IN/dwi_mask_upsampled.mif ../template/mask_input/PRE.mif + foreach `ls -d *patient | sort -R | tail -20` : ln -sr IN/wmfod.mif ../template/fod_input/PRE.mif ";" ln -sr IN/dwi_mask_upsampled.mif ../template/mask_input/PRE.mif + foreach `ls -d *control | sort -R | tail -20` : ln -sr IN/wmfod.mif ../template/fod_input/PRE.mif ";" ln -sr IN/dwi_mask_upsampled.mif ../template/mask_input/PRE.mif .. include:: common_fba_steps/population_template2.rst @@ -141,7 +141,7 @@ Alternatively, if you have more than 40 subjects you can randomly select a subse Register the FOD image from all subjects to the FOD template image:: - foreach * : mrregister IN/fod.mif -mask1 IN/dwi_mask_upsampled.mif ../template/fod_template.mif -nl_warp IN/subject2template_warp.mif IN/template2subject_warp.mif + foreach * : mrregister IN/wmfod.mif -mask1 IN/dwi_mask_upsampled.mif ../template/wmfod_template.mif -nl_warp IN/subject2template_warp.mif IN/template2subject_warp.mif 12. Compute the intersection of all subject masks in template space @@ -156,7 +156,7 @@ Here we perform a 2-step threshold to identify template white matter fixels to b Compute a template AFD peaks fixel image:: - fod2fixel ../template/fod_template.mif -mask ../template/mask_intersection.mif ../template/fixel_temp -peak peaks.mif + fod2fixel ../template/wmfod_template.mif -mask ../template/mask_intersection.mif ../template/fixel_temp -peak peaks.mif .. NOTE:: Fixel images in this step are stored using the :ref:`fixel_format`, which exploits the filesystem to store all fixel data in a directory. @@ -173,7 +173,7 @@ Generate an analysis voxel mask from the fixel mask. The median filter in this s Recompute the fixel mask using the analysis voxel mask. Using the mask allows us to use a lower AFD threshold than possible in the steps above, to ensure we have included fixels with low AFD inside white matter (e.g. areas with fibre crossings):: - fod2fixel -mask ../template/voxel_mask.mif -fmls_peak_value 0.2 ../template/fod_template.mif ../template/fixel_mask + fod2fixel -mask ../template/voxel_mask.mif -fmls_peak_value 0.2 ../template/wmfod_template.mif ../template/fixel_mask .. NOTE:: We recommend having no more than 500,000 fixels in the analysis fixel mask (you can check this by :code:`mrinfo -size ../template/fixel/mask.mif`, and looking at the size of the image along the 1st dimension), otherwise downstream statistical analysis (using :ref:`fixelcfestats`) will run out of RAM). A mask with 500,000 fixels will require a PC with 128GB of RAM for the statistical analysis step. To reduce the number of fixels, try changing the thresholds in this step, or reduce the extent of upsampling in step 7. @@ -182,7 +182,7 @@ Recompute the fixel mask using the analysis voxel mask. Using the mask allows us Note that here we warp FOD images into template space *without* FOD reorientation. Reorientation will be performed in a separate subsequent step:: - foreach * : mrtransform IN/fod.mif -warp IN/subject2template_warp.mif -noreorientation IN/fod_in_template_space.mif + foreach * : mrtransform IN/wmfod.mif -warp IN/subject2template_warp.mif -noreorientation IN/fod_in_template_space.mif 15. Segment FOD images to estimate fixels and their apparent fibre density (FD) From 840beacf53872a2e9e1eff53211ee6f8eba3e810 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 4 Jul 2017 12:55:00 +1000 Subject: [PATCH 047/139] fixelcfestats: Minor tweaks --- cmd/fixelcfestats.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index ac06fb7ac6..14adb18c98 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -173,7 +173,7 @@ void run() { mask_fixels = 0; for (mask.index(0) = 0; mask.index(0) != num_fixels; ++mask.index(0)) fixel2row[mask.index(0)] = mask.value() ? mask_fixels++ : -1; - CONSOLE ("Mask contains " + str(mask_fixels) + " fixels"); + CONSOLE ("Fixel mask contains " + str(mask_fixels) + " fixels"); } else { Header data_header; data_header.ndim() = 3; @@ -183,10 +183,12 @@ void run() { data_header.spacing(0) = data_header.spacing(1) = data_header.spacing(2) = NaN; data_header.stride(0) = 1; data_header.stride(1) = 2; data_header.stride(2) = 3; mask = Image::scratch (data_header, "scratch fixel mask"); - for (mask.index(0) = 0; mask.index(0) != num_fixels; ++mask.index(0)) + for (index_type f = 0; f != num_fixels; ++f) { + mask.index(0) = f; mask.value() = true; + } mask_fixels = num_fixels; - for (uint32_t f = 0; f != num_fixels; ++f) + for (index_type f = 0; f != num_fixels; ++f) fixel2row[f] = f; } @@ -306,6 +308,14 @@ void run() { gaussian_const1 = 1.0 / (smooth_std_dev * std::sqrt (2.0 * Math::pi)); } + for (size_t f = 0; f != num_fixels; ++f) { + mask.index(0) = f; + if (!mask.value()) { + TRACE; + throw Exception ("False value in mask"); + } + } + { ProgressBar progress ("normalising and thresholding fixel-fixel connectivity matrix", num_fixels); for (index_type fixel = 0; fixel < num_fixels; ++fixel) { @@ -493,6 +503,9 @@ void run() { uncorrected_pvalues_neg.reset (new vector_type (mask_fixels)); } + // FIXME fixelcfestats is hanging here for some reason... + // Even when no mask is supplied + if (permutations.size()) { Stats::PermTest::run_permutations (permutations, glm_ttest, cfe_integrator, empirical_cfe_statistic, cfe_output, cfe_output_neg, From 726496edf86ecdfbe5cc68dd6530105f6b790e87 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 4 Jul 2017 14:41:17 +1000 Subject: [PATCH 048/139] update FBA docs take 2 includes reintroduction of dwibiascorrect in the multi-tissue FBA pipeline: I've found in several studies that this has an important impact on both mask as well as response function estimation (the latter applies specifically to the dwi2response tournier algorithm). I've also changes the ANTS parameters: the new ones greatly reduce the corrected intensity of non-brain bits in the lower frontal regions of the head, which again greatly aids the mask estimation so it doesn't include too much crap. --- bin/dwibiascorrect | 2 +- .../mt_fibre_density_cross-section.rst | 76 ++++++++++--------- .../ss_fibre_density_cross-section.rst | 7 -- 3 files changed, 42 insertions(+), 43 deletions(-) diff --git a/bin/dwibiascorrect b/bin/dwibiascorrect index 4b701d9713..7fc3ac307f 100755 --- a/bin/dwibiascorrect +++ b/bin/dwibiascorrect @@ -118,7 +118,7 @@ elif app.args.ants: run.command('mrconvert mean_bzero.mif mean_bzero.nii -stride +1,+2,+3') run.command('mrconvert mask.mif mask.nii -stride +1,+2,+3') bias_path = 'bias.nii' - run.command('N4BiasFieldCorrection -d 3 -i mean_bzero.nii -w mask.nii -o [corrected.nii,' + bias_path + '] -s 2 -b [150] -c [200x200,0.0]') + run.command('N4BiasFieldCorrection -d 3 -i mean_bzero.nii -w mask.nii -o [corrected.nii,' + bias_path + '] -b [150,3] -c [1000x1000,0.0]') run.command('mrcalc in.mif ' + bias_path + ' -div result.mif') run.command('mrconvert result.mif ' + path.fromUser(app.args.output, True) + (' -force' if app.force else '')) diff --git a/docs/fixel_based_analysis/mt_fibre_density_cross-section.rst b/docs/fixel_based_analysis/mt_fibre_density_cross-section.rst index 03fb443705..463a170388 100644 --- a/docs/fixel_based_analysis/mt_fibre_density_cross-section.rst +++ b/docs/fixel_based_analysis/mt_fibre_density_cross-section.rst @@ -14,7 +14,7 @@ All steps in this tutorial have written as if the commands are being **run on a study/subjects/002_control/dwi.mif study/subjects/002_control/wmfod.mif -.. NOTE:: All commands in this tutorial are run **from the subjects path** up until step 18, where we change directory to the template path +.. NOTE:: All commands in this tutorial are run **from the subjects path** up until step 19, where we change directory to the template path For all MRtrix scripts and commands, additional information on the command usage and available command-line options can be found by invoking the command with the :code:`-help` option. Please post any questions or issues on the `MRtrix community forum `_. @@ -32,57 +32,63 @@ Pre-processsing steps .. include:: common_fba_steps/dwipreproc.rst +3. Bias field correction +^^^^^^^^^^^^^^^^^^^^^^^^ +Bias field correction is important to deal with spatial intensity inhomogeneities. Even though this FBA pipeline will account for these as well (in the later :ref:`mtlognorm` step, which is furthermore crucial to correct for global intensity differences between subjects), performing bias field correction at this stage will allow for more accurate estimation of the tissue response functions as well as the individual subject brain masks. + +This can be done in a single step using the :ref:`dwibiascorrect` script in MRtrix. The script uses bias field correction algorthims available in `ANTS `_ or `FSL `_. In our experience the `N4 algorithm `_ in ANTS gives far superior results. To install N4, install the `ANTS `_ package, then perform bias field correction on DW images using:: + + foreach * : dwibiascorrect -ants -mask IN/dwi_mask.mif IN/dwi_denoised_preproc.mif IN/dwi_denoised_preproc_bias.mif + Fixel-based analysis steps --------------------------- -3. Computing group average tissue response functions +4. Computing group average tissue response functions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ As described `here `__, using the same response function when estimating FOD images for all subjects enables differences in the intra-axonal volume (and therefore DW signal) across subjects to be detected as differences in the FOD amplitude (the AFD). To ensure the response function is representative of your study population, a group average response function can be computed by first estimating a response function per subject, then averaging with the script:: - foreach * : dwi2response dhollander IN/dwi_denoised_preproc.mif IN/response_wm.txt IN/response_gm.txt IN/response_csf.txt + foreach * : dwi2response dhollander IN/dwi_denoised_preproc_bias.mif IN/response_wm.txt IN/response_gm.txt IN/response_csf.txt average_response */response_wm.txt ../group_average_response_wm.txt average_response */response_gm.txt ../group_average_response_gm.txt average_response */response_csf.txt ../group_average_response_csf.txt -4. Upsampling DW images +5. Upsampling DW images ^^^^^^^^^^^^^^^^^^^^^^^ - Upsampling DWI data before computing FODs can `increase anatomical contrast `_ and improve downstream spatial normalisation and statistics. We recommend upsampling to a voxel size of 1.25mm (for human brains). If you have data that has smaller voxels than 1.25mm, then we recommend you can skip this step:: - foreach * : mrresize IN/dwi_denoised_preproc.mif -vox 1.25 IN/dwi_denoised_preproc_upsampled.mif + foreach * : mrresize IN/dwi_denoised_preproc_bias.mif -vox 1.25 IN/dwi_denoised_preproc_bias_upsampled.mif -5. Compute upsampled brain mask images +6. Compute upsampled brain mask images ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Compute a whole brain mask from the upsampled DW images:: - foreach * : dwi2mask IN/dwi_denoised_preproc_upsampled.mif IN/dwi_mask_upsampled.mif - -Depending on your data, you may find that computing masks on native resolution DWIs gives superiour masks (with less holes), then upsampling. This can be performed using:: - - foreach * : dwi2mask IN/dwi_denoised_preproc.mif - \| mrresize - -scale 2.0 -interp nearest IN/dwi_mask_upsampled.mif + foreach * : dwi2mask IN/dwi_denoised_preproc_bias_upsampled.mif IN/dwi_mask_upsampled.mif - -6. Fibre Orientation Distribution estimation +7. Fibre Orientation Distribution estimation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When performing analysis of AFD, Constrained Spherical Deconvolution (CSD) should be performed using the group average response functions computed at step 3:: - foreach * : dwi2fod msmt_csd IN/dwi_denoised_preproc_upsampled.mif ../group_average_response_wm.txt IN/wmfod.mif ../group_average_response_gm.txt IN/gm.mif ../group_average_response_csf.txt IN/csf.mif -mask IN/dwi_mask_upsampled.mif + foreach * : dwi2fod msmt_csd IN/dwi_denoised_preproc_bias_upsampled.mif ../group_average_response_wm.txt IN/wmfod.mif ../group_average_response_gm.txt IN/gm.mif ../group_average_response_csf.txt IN/csf.mif -mask IN/dwi_mask_upsampled.mif -7. Perform simultaneous bias field correction and intensity normalisation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This step performs :ref:`global intensity normalisation ` by scaling all tissue types with a spatially smoothly varying normalisation field:: +8. Intensity normalisation +^^^^^^^^^^^^^^^^^^^^^^^^^^ +This step performs :ref:`global intensity normalisation ` in the log-domain by scaling all tissue types with a spatially smoothly varying normalisation field:: foreach * : mtlognorm IN/wmfod.mif IN/wmfod_norm.mif IN/gm.mif IN/gm_norm.mif IN/csf.mif IN/csf_norm.mif -mask IN/dwi_mask_upsampled.mif +If CSD was performed with the same single set of (average) WM, GM and CSF response functions for all subjects, then the resulting output of :ref:`mtlognorm` should make the amplitudes comparable between those subjects as well. + +Note that this step is crucial in the FBA pipeline, even if bias field correction was applied during the preprocessing stage, as the latter does not correct for global intensity differences between subjects. + .. WARNING:: We recommend you that you check that the normalisation scale (computed during intensity normalisation) is not influenced by the variable of interest in your study. For example if one group contains global (widespread) changes in white matter T2, then this may directly influence the intensity normalisation and therefore bias downstream analysis of apparent fibre density (FD). To check this, you can perform an equivalence test to ensure the overall normalisation scale does not differ between groups. To output these overall normalisation scales for all subjects use :code:`mrinfo */wmfod_norm.mif -property lognorm_scale`. -8. Generate a study-specific unbiased FOD template -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +9. Generate a study-specific unbiased FOD template +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: common_fba_steps/population_template.rst @@ -98,21 +104,21 @@ Alternatively, if you have more than 40 subjects you can randomly select a subse .. include:: common_fba_steps/population_template2.rst -9. Register all subject FOD images to the FOD template +10. Register all subject FOD images to the FOD template ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Register the FOD image from all subjects to the FOD template image. Note you can skip this step if you built your template from your entire population and saved the warps (see previous step):: foreach * : mrregister IN/wmfod_norm.mif -mask1 IN/dwi_mask_upsampled.mif ../template/wmfod_template.mif -nl_warp IN/subject2template_warp.mif IN/template2subject_warp.mif -10. Compute the intersection of all subject masks in template space +11. Compute the intersection of all subject masks in template space ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: common_fba_steps/mask_intersection.rst -11. Compute a white matter analysis voxel & fixel mask -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +12. Compute a white matter analysis voxel & fixel mask +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Here we first identify all voxels having some white matter by thresholding the DC term (first SH coefficient) of the multi-tissue FOD image:: mrconvert ../template/wmfod_template.mif -coord 3 0 - | mrthreshold - ../template/voxel_mask.mif @@ -126,57 +132,57 @@ You can visualise the output fixels using the fixel plot tool from :ref:`mrview` .. NOTE:: We recommend having no more than 500,000 fixels in the analysis_fixel_mask (you can check this by :code:`mrinfo -size ../template/fixel_mask/directions.mif`, and looking at the size of the image along the 1st dimension), otherwise downstream statistical analysis (using :ref:`fixelcfestats`) may run out of RAM). A mask with 500,000 fixels will require a PC with 128GB of RAM for the statistical analysis step. To reduce the number of fixels, try either reducing the number of voxels in the voxel mask by applying a manual threshold using :code:`-abs`, increasing the :code:`-fmls_peak_value`, or reducing the extent of upsampling in step 4. -12. Warp FOD images to template space -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +13. Warp FOD images to template space +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Note that here we warp FOD images into template space *without* FOD reorientation. Reorientation will be performed in a separate subsequent step:: foreach * : mrtransform IN/wmfod_norm.mif -warp IN/subject2template_warp.mif -noreorientation IN/fod_in_template_space.mif -13. Segment FOD images to estimate fixels and their apparent fibre density (FD) +14. Segment FOD images to estimate fixels and their apparent fibre density (FD) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: common_fba_steps/compute_AFD.rst -14. Reorient fixel orientations +15. Reorient fixel orientations ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: common_fba_steps/reorient_fixels.rst -15. Assign subject fixels to template fixels +16. Assign subject fixels to template fixels ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In step 8 & 9 we obtained spatial correspondence between subject and template. In step 14 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, you will note that the output fixel directory is the same for all subjects. This directory now stores data for all subjects at corresponding fixels, ready for input to :code:`fixelcfestats` in step 20 below:: foreach * : fixelcorrespondence IN/fixel_in_template_space/fd.mif ../template/fixel_mask ../template/fd PRE.mif -16. Compute fibre cross-section (FC) metric +17. Compute fibre cross-section (FC) metric ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: common_fba_steps/compute_FC.rst -17. Compute a combined measure of fibre density and cross-section (FDC) +18. Compute a combined measure of fibre density and cross-section (FDC) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: common_fba_steps/compute_FDC.rst -18. Perform whole-brain fibre tractography on the FOD template +19. Perform whole-brain fibre tractography on the FOD template ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: common_fba_steps/tractography.rst -19. Reduce biases in tractogram densities +20. Reduce biases in tractogram densities ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: common_fba_steps/sift.rst -20. Perform statistical analysis of FD, FC, and FDC +21. Perform statistical analysis of FD, FC, and FDC ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: common_fba_steps/statistics.rst -21. Visualise the results +22. Visualise the results ^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: common_fba_steps/visualisation.rst diff --git a/docs/fixel_based_analysis/ss_fibre_density_cross-section.rst b/docs/fixel_based_analysis/ss_fibre_density_cross-section.rst index ebd19e6a0d..c4ec1f9241 100644 --- a/docs/fixel_based_analysis/ss_fibre_density_cross-section.rst +++ b/docs/fixel_based_analysis/ss_fibre_density_cross-section.rst @@ -96,23 +96,16 @@ Fixel-based analysis steps 7. Upsampling DW images ^^^^^^^^^^^^^^^^^^^^^^^ - Upsampling DWI data before computing FODs can `increase anatomical contrast `_ and improve downstream spatial normalisation and statistics. We recommend upsampling to a voxel size of 1.25mm (for human brains). If you have data that has smaller voxels than 1.25mm, then we recommend you can skip this step:: foreach * : mrresize IN/dwi_denoised_preproc_bias_norm.mif -vox 1.25 IN/dwi_denoised_preproc_bias_norm_upsampled.mif 8. Compute upsampled brain mask images ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Compute a whole brain mask from the upsampled DW images:: foreach * : dwi2mask IN/dwi_denoised_preproc_bias_norm_upsampled.mif IN/dwi_mask_upsampled.mif -Depending on your data, you may find that upsampling the low-resolution masks from step 3 gives superiour masks (with less holes). This can be performed using:: - - foreach * : mrresize IN/dwi_mask.mif -scale 2.0 -inter nearest IN/dwi_mask_upsampled.mif - - 9. Fibre Orientation Distribution estimation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When performing analysis of AFD, Constrained Spherical Deconvolution (CSD) should be performed using the group average response function computed at step . If not using AFD in the fixel-based analysis (and therefore you have skipped steps 4-6), however you still want to compute FODs for image registration, then you can use a subject-specific response function. Note that :code:`dwi2fod csd` can be used, however here we use :code:`dwi2fod msmt_csd` (even with single shell data) to benefit from the hard non-negativity constraint:: From fb9d512e3622367876be16840b248a25b6bcfafa Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 4 Jul 2017 14:46:15 +1000 Subject: [PATCH 049/139] update FBA docs take 2.5 dwibiascorrect computes its own initial mask internally --- docs/fixel_based_analysis/mt_fibre_density_cross-section.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fixel_based_analysis/mt_fibre_density_cross-section.rst b/docs/fixel_based_analysis/mt_fibre_density_cross-section.rst index 463a170388..0bcfbff6a4 100644 --- a/docs/fixel_based_analysis/mt_fibre_density_cross-section.rst +++ b/docs/fixel_based_analysis/mt_fibre_density_cross-section.rst @@ -38,7 +38,7 @@ Bias field correction is important to deal with spatial intensity inhomogeneitie This can be done in a single step using the :ref:`dwibiascorrect` script in MRtrix. The script uses bias field correction algorthims available in `ANTS `_ or `FSL `_. In our experience the `N4 algorithm `_ in ANTS gives far superior results. To install N4, install the `ANTS `_ package, then perform bias field correction on DW images using:: - foreach * : dwibiascorrect -ants -mask IN/dwi_mask.mif IN/dwi_denoised_preproc.mif IN/dwi_denoised_preproc_bias.mif + foreach * : dwibiascorrect -ants IN/dwi_denoised_preproc.mif IN/dwi_denoised_preproc_bias.mif Fixel-based analysis steps From 5fdfa2c97e8111150e721b544d3fce75258dd94a Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 4 Jul 2017 16:01:11 +1000 Subject: [PATCH 050/139] update global intensity normalisation doc --- docs/concepts/global_intensity_normalisation.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/concepts/global_intensity_normalisation.rst b/docs/concepts/global_intensity_normalisation.rst index 18b0a8f516..b1d02c3365 100644 --- a/docs/concepts/global_intensity_normalisation.rst +++ b/docs/concepts/global_intensity_normalisation.rst @@ -4,14 +4,13 @@ Global intensity normalisation ============================== -Most DWI models derive quantitative measures by using the ratio of the DW signal to the b=0 signal within each voxel. This voxel-wise b=0 normalisation implicitly removes intensity variations due to T2-weighting and RF inhomogeneity. However, unless all compartments within white matter (e.g. intra- and extra-axonal space, myelin, cerebral spinal fluid (CSF) and grey matter partial volumes) are modelled accurately (i.e. with appropriate assumptions/modelling of both the compartment diffusion and T2), the proportion of one compartment in a voxel may influence another. For example, if CSF partial volume at the border of white matter and the ventricles is not taken into account, then a voxel-wise normalisation performed by dividing by the b=0 (which has a long T2 and appears brighter in CSF than white matter in the T2-weighted b=0 image), will artificially reduce the DW signal from the white matter intra-axonal (restricted) compartment, ultimately changing the derived quantiative measures. Multi-compartment diffusion MRI models aspire to model many/all of the compartments as accurately as possible. However, in practice current models are limited by restrictions/assumptions placed on the different compartments, and all require multiple b-value acquisitions and therefore longer scan times. +Most DWI models derive quantitative measures by using the ratio of the DW signal to the b=0 signal within each voxel. This voxel-wise b=0 normalisation implicitly removes intensity variations due to T2-weighting and RF inhomogeneity. However, unless all compartments within white matter (e.g. intra- and extra-axonal space, myelin, cerebral spinal fluid (CSF) and grey matter partial volumes) are modelled accurately (i.e. with appropriate assumptions/modelling of both the compartment diffusion and T2), the proportion of one compartment in a voxel may influence another. For example, if CSF partial volume at the border of white matter and the ventricles is not taken into account, then a voxel-wise normalisation performed by dividing by the b=0 (which has a long T2 and appears brighter in CSF than white matter in the T2-weighted b=0 image), will artificially reduce the DW signal from the white matter intra-axonal (restricted) compartment, ultimately changing the derived quantitative measures. -In our previous work investigating differences in `Apparent Fibre Density (AFD) `_ we opt to perform a *global intensity normalisation*. This avoids the aforementioned issues, but also comes with its own set of problems and assumptions. Aside from the problem of how to select the region to perform global intensity normalisation (that is unbiased with respect to the groups in the analysis), the data must also be first bias field corrected to eliminate low frequency intensity inhomogeneities across the image. +In our previous work investigating differences in `Apparent Fibre Density (AFD) `_ we opt to perform a *global intensity normalisation* between subjects. This avoids the aforementioned issues, but also comes with its own set of challenges and assumptions inherent to specific strategies to deal with this. Aside from the problem of how to select the region to perform global intensity normalisation (that is unbiased with respect to the groups in the analysis), the data must also be bias field corrected first, to eliminate low frequency intensity inhomogeneities across the image. - -A sound approach to global intensity normalisation would be to normalise based on the median CSF b=0 intensity across all subjects (on the assumption that the CSF T2 is unlikely to be affected by pathology). However, in practice it is difficult to obtain a robust partial-volume-free estimate of the CSF intensity due to the typical low resolution of DW images. For participants less than 50 years old (with reasonably small ventricles), it can be difficult to identify pure CSF voxels at 2-2.5mm resolutions. We therefore recommend performing a global intensity normalisation using the median white matter b=0 intensity. While the white matter b=0 intensity may be influenced by pathology-induced changes in T2, our assumption is that such changes will be local to the pathology and therefore have little influence on the median b=0 value. +In theory, a sound approach to global intensity normalisation would be to normalise based on the median CSF b=0 intensity across all subjects (on the assumption that the CSF T2 is unlikely to be affected by pathology). However, in practice it is surprisingly difficult to obtain a robust partial-volume-free estimate of the CSF intensity due to the typical low resolution of DW images. For participants less than 50 years old, due to reasonably small ventricles, it can be difficult to identify pure CSF voxels at 2-2.5mm resolutions. We therefore recommend performing a global intensity normalisation using the *median* white matter b=0 intensity. While the white matter b=0 intensity may be influenced by pathology-induced changes in T2, our assumption is that such changes will be local to the pathology and therefore have little influence on the median b=0 value. We have included the :ref:`dwiintensitynorm` script in MRtrix to perform an automatic global normalisation using the median white matter b=0 value. The script input requires two folders: a folder containing all DW images in the study (in .mif format) and a folder containing the corresponding whole brain mask images (with the same filename prefix). The script runs by first computing diffusion tensor Fractional Anisotropy (FA) maps, registering these to a study-specific template, then thresholding the template FA map to obtain an approximate white matter mask. The mask is then transformed back into the space of each subject image and used in the :ref:`dwinormalise` command to normalise the input DW images to have the same b=0 white matter median value. All intensity normalised data will be output in a single folder. As previously mentioned all DWI data must be bias field corrected before using :ref:`dwiintensitynorm`, for example using :code:`dwibiascorrect`. -As an alternative to the :code:`dwiintensitynorm` script, we have recently provided a new command called :ref:`mtbin`, which simultaneously performs bias field correction and global intensity normalisation on multi-tissue CSD compartments. The benefit of the :ref:`mtbin` command is that normalisation can be performed independently on each subject, and therefore does not require a computationally expensive registration step to a group template. However, to perform multi-tissue CSD currently requires DWI data with multiple b-values, and therefore the :ref:`mtbin` method is currently not suitable for single-shell DWI. +As an alternative to the :code:`dwiintensitynorm` script, we have recently provided a new command called :ref:`mtlognorm`, which performs multi-tissue informed intensity normalisation in the log-domain. The benefit of the :ref:`mtlognorm` command is that normalisation can be performed independently on each subject, and therefore does not require a computationally expensive registration step to a group template. However, to perform multi-tissue CSD with 3 tissue types (WM, GM, CSF) currently requires DWI data with multiple b-values (this will change at some stage, when the implementation of single-shell 3-tissue CSD becomes available). From dfc36c80dc8e95920579e29b533c767435ebba15 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Wed, 5 Jul 2017 11:39:55 +1000 Subject: [PATCH 051/139] reinstate mtbin Use at your own risk; I have extensive scientific findings to support that it is probably wise not to use it. --- cmd/mtbin.cpp | 334 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 322 insertions(+), 12 deletions(-) diff --git a/cmd/mtbin.cpp b/cmd/mtbin.cpp index 4aaae17425..d91b36da06 100644 --- a/cmd/mtbin.cpp +++ b/cmd/mtbin.cpp @@ -14,48 +14,358 @@ #include "command.h" #include "image.h" +#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" +#include "algo/threaded_copy.h" using namespace MR; using namespace App; +#define DEFAULT_NORM_VALUE 0.282094 +#define DEFAULT_MAXITER_VALUE 100 void usage () { - AUTHOR = ""; + AUTHOR = "David Raffelt (david.raffelt@florey.edu.au), Rami Tabbara (rami.tabbara@florey.edu.au) and Thijs Dhollander (thijs.dhollander@gmail.com)"; - SYNOPSIS = "Please use the new mtlognorm command instead."; + SYNOPSIS = "Multi-Tissue Bias field correction and Intensity Normalisation (MTBIN)"; DESCRIPTION - + "WARNING: there were some major issues with this method. Please start using the new mtlognorm command instead."; + + "This command inputs N number of tissue components " + "(e.g. from multi-tissue CSD), and outputs N corrected tissue components. Intensity normalisation is performed by either " + "determining a common global normalisation factor for all tissue types (default) or by normalising each tissue type independently " + "with a single tissue-specific global scale factor." + + + "The -mask option is mandatory, and is optimally provided with a brain mask, such as the one obtained from dwi2mask earlier in the processing pipeline." + + + "Example usage: mtbin wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif." + + + "The estimated multiplicative bias field is guaranteed to have a mean of 1 over all voxels within the mask."; ARGUMENTS - + Argument ("input output", "").type_image_in().allow_multiple(); + + 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", "").required () + + Option ("mask", "define the mask to compute the normalisation within. This option is mandatory.").required () + Argument ("image").type_image_in () - + Option ("value", "") + + Option ("value", "specify the value to which the summed tissue compartments will be normalised to " + "(Default: sqrt(1/(4*pi)) = " + str(DEFAULT_NORM_VALUE, 6) + ")") + Argument ("number").type_float () - + Option ("bias", "") + + Option ("bias", "output the estimated bias field") + Argument ("image").type_image_out () - + Option ("independent", "") + + Option ("independent", "intensity normalise each tissue type independently") - + Option ("maxiter", "") + + 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() - + Option ("check", "") + + Option ("check", "check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure. However, these regions are still corrected for bias fields based on the other image data.") + Argument ("image").type_image_out (); } +const int n_basis_vecs (20); + + +FORCE_INLINE Eigen::MatrixXd basis_function (const Eigen::Vector3 pos) { + 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; + 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; + basis(19) = x * y * z; + return basis; +} + +// Currently not used, but keep if we want to make mask argument optional in the future +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); +} + + +FORCE_INLINE void refine_mask (Image& summed, + Image& initial_mask, + Image& refined_mask) { + + for (auto i = Loop (summed, 0, 3) (summed, initial_mask, refined_mask); i; ++i) { + if (std::isfinite((float) summed.value ()) && summed.value () > 0.f && initial_mask.value ()) + refined_mask.value () = true; + else + refined_mask.value () = false; + } +} 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"); - throw Exception ("There were some major issues with this method. Please start using the new mtlognorm command instead."); + if (argument.size() < 4) + throw Exception ("At least two tissue types must be provided"); -} + ProgressBar progress ("performing intensity normalisation and bias field correction..."); + vector > input_images; + vector
output_headers; + vector output_filenames; + + + // Open input images and check for output + for (size_t i = 0; i < argument.size(); i += 2) { + progress++; + 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); + + 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])); + output_filenames.push_back (argument[i + 1]); + } + + // Load the mask + Header header_3D (input_images[0]); + header_3D.ndim() = 3; + auto opt = get_options ("mask"); + + auto orig_mask = Image::open (opt[0][0]); + auto initial_mask = Image::scratch (orig_mask); + auto mask = Image::scratch (orig_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]); i; ++i) + summed.value() += input_images[j].value(); + progress++; + } + + // Refine the initial mask to exclude negative summed tissue components + refine_mask (summed, orig_mask, initial_mask); + + threaded_copy (initial_mask, mask); + + size_t num_voxels = 0; + for (auto i = Loop (mask) (mask); i; ++i) { + if (mask.value()) + num_voxels++; + } + 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 + 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 = 1; + bool converged = false; + + // Iterate until convergence or max iterations performed + while (!converged && iter < max_iter) { + + INFO ("iteration: " + str(iter)); + // 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++; + + INFO ("scale factors: " + str(scale_factors.transpose())); + + + // Solve for bias field weights + Transform transform (mask); + Eigen::MatrixXd bias_field_basis (num_voxels, n_basis_vecs); + 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++; + bias_field_weights = bias_field_basis.colPivHouseholderQr().solve(y); + 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; + + progress++; + + // Check for convergence + Eigen::MatrixXd diff; + if (iter > 1) { + diff = previous_scale_factors.array() - scale_factors.array(); + diff = diff.array().abs() / previous_scale_factors.array(); + INFO ("percentage change in estimated scale factors: " + str(diff.mean() * 100)); + if (diff.mean() < 0.001) + converged = true; + } + + // Re-evaluate mask + if (!converged) { + 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(); + } + } + + refine_mask (summed, initial_mask, mask); + + 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++; + } + + 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"); + 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 + 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_image.value() = scale_factors(j, 0) * input_images[j].value() / bias_field.value(); + } + } +} From f7460550e0dfbed46775f0131c3ce88916a3647c Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 5 Jul 2017 13:49:12 +1000 Subject: [PATCH 052/139] mrview Fixel plot: Compatibility with fixel data files containing NaNs --- src/gui/mrview/tool/fixel/base_fixel.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/gui/mrview/tool/fixel/base_fixel.cpp b/src/gui/mrview/tool/fixel/base_fixel.cpp index 0084937d0d..a21333dae5 100644 --- a/src/gui/mrview/tool/fixel/base_fixel.cpp +++ b/src/gui/mrview/tool/fixel/base_fixel.cpp @@ -118,35 +118,37 @@ namespace MR "void main() {\n"; if (fixel.use_discard_lower()) - source += " if (v_threshold[0] < lower) return;\n"; + source += " if (v_threshold[0] < lower || isnan(v_threshold[0])) return;\n"; if (fixel.use_discard_upper()) - source += " if (v_threshold[0] > upper) return;\n"; + source += " if (v_threshold[0] > upper || isnan(v_threshold[0])) return;\n"; switch (scale_type) { case Unity: - source += " vec4 line_offset = length_mult * vec4 (v_dir[0], 0);\n"; + 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"; + source += " if (isnan(v_scale[0])) return;\n" + " 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 ("; + source += " if (isnan(v_colour[0])) return;\n" + " 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") + + std::string (" vec3 color;\n") + ColourMap::maps[colourmap].glsl_mapping + - " fColour = color;\n"; + " fColour = color;\n"; break; case Direction: source += - " fColour = normalize (abs (v_dir[0]));\n"; + " fColour = normalize (abs (v_dir[0]));\n"; break; default: break; From 07289db7c101a4d4f2bb9e2a66cf0163629de218 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 5 Jul 2017 13:54:04 +1000 Subject: [PATCH 053/139] Stats::CFE::NormMatrixElement class: Eigen 3.3 compatibility --- src/stats/cfe.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stats/cfe.h b/src/stats/cfe.h index f6029d1a89..5c62d8cb7d 100644 --- a/src/stats/cfe.h +++ b/src/stats/cfe.h @@ -55,7 +55,7 @@ namespace MR // A class to store fixel index / connectivity value pairs // only after the connectivity matrix has been thresholded / normalised class NormMatrixElement - { + { NOMEMALIGN public: NormMatrixElement (const index_type fixel_index, const connectivity_value_type connectivity_value) : From 0f13690f74a0abab0a995151118ca52ba5d97571 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 5 Jul 2017 16:33:51 +1000 Subject: [PATCH 054/139] fixelcfestats: Remove vestigial debugging call --- cmd/fixelcfestats.cpp | 8 -------- docs/reference/commands/fixelcfestats.rst | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 14adb18c98..800c8a6bdd 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -308,14 +308,6 @@ void run() { gaussian_const1 = 1.0 / (smooth_std_dev * std::sqrt (2.0 * Math::pi)); } - for (size_t f = 0; f != num_fixels; ++f) { - mask.index(0) = f; - if (!mask.value()) { - TRACE; - throw Exception ("False value in mask"); - } - } - { ProgressBar progress ("normalising and thresholding fixel-fixel connectivity matrix", num_fixels); for (index_type fixel = 0; fixel < num_fixels; ++fixel) { diff --git a/docs/reference/commands/fixelcfestats.rst b/docs/reference/commands/fixelcfestats.rst index 209c92d544..da3d415250 100644 --- a/docs/reference/commands/fixelcfestats.rst +++ b/docs/reference/commands/fixelcfestats.rst @@ -94,7 +94,7 @@ Raffelt, D.; Smith, RE.; Ridgway, GR.; Tournier, JD.; Vaughan, DN.; Rose, S.; He -**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-2017 the MRtrix3 contributors. From a595b3e42b187c7f23c99372b4658e9f5c57cf63 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Fri, 7 Jul 2017 16:19:56 +1000 Subject: [PATCH 055/139] mtbin --> deprecated with warning, still works, though potential to cause scientific nonsense in an automated FBA; better be careful --- cmd/mtbin.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/cmd/mtbin.cpp b/cmd/mtbin.cpp index d91b36da06..d2baed31c7 100644 --- a/cmd/mtbin.cpp +++ b/cmd/mtbin.cpp @@ -33,19 +33,10 @@ void usage () { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au), Rami Tabbara (rami.tabbara@florey.edu.au) and Thijs Dhollander (thijs.dhollander@gmail.com)"; - SYNOPSIS = "Multi-Tissue Bias field correction and Intensity Normalisation (MTBIN)"; + SYNOPSIS = "Multi-Tissue Bias field correction and Intensity Normalisation (WARNING: deprecated)."; DESCRIPTION - + "This command inputs N number of tissue components " - "(e.g. from multi-tissue CSD), and outputs N corrected tissue components. Intensity normalisation is performed by either " - "determining a common global normalisation factor for all tissue types (default) or by normalising each tissue type independently " - "with a single tissue-specific global scale factor." - - + "The -mask option is mandatory, and is optimally provided with a brain mask, such as the one obtained from dwi2mask earlier in the processing pipeline." - - + "Example usage: mtbin wm.mif wm_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif." - - + "The estimated multiplicative bias field is guaranteed to have a mean of 1 over all voxels within the mask."; + + "WARNING: this command is deprecated and may produce inappropriate results in several cases. Please use the new mtnormalise."; ARGUMENTS + Argument ("input output", "list of all input and output tissue compartment files. See example usage in the description. " @@ -133,6 +124,9 @@ FORCE_INLINE void refine_mask (Image& summed, void run () { + + WARN ("This command is deprecated and may produce inappropriate results in several cases. Please use the new mtnormalise."); + 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"); From 8b5d1ecf483487fc29a52179656145e730a5dac2 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Fri, 7 Jul 2017 16:21:49 +1000 Subject: [PATCH 056/139] delete old mtnormalise: won't break anyone's pipeline in this universe, since no one was using it or knew of its existence, apart from ourselves maybe, but we are careful people that know about this change --- cmd/mtnormalise.cpp | 138 -------------------------------------------- 1 file changed, 138 deletions(-) delete mode 100644 cmd/mtnormalise.cpp diff --git a/cmd/mtnormalise.cpp b/cmd/mtnormalise.cpp deleted file mode 100644 index 27d2c082f9..0000000000 --- a/cmd/mtnormalise.cpp +++ /dev/null @@ -1,138 +0,0 @@ -/* Copyright (c) 2008-2017 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 http://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; - -#define DEFAULT_NORM_VALUE 0.282094 - -void usage () -{ - AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; - - SYNOPSIS = "Multi-tissue normalise"; - - DESCRIPTION - + "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"; - - 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 (); -} - - -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"); - - vector> input_images; - vector
output_headers; - vector output_filenames; - - 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, 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); - // 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; - auto opt = get_options("mask"); - if (opt.size()) { - mask = Image::open (opt[0][0]); - } else { - auto summed = Image::scratch (input_images[0], "summed 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(); - } - 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++; - } - - const float normalisation_value = get_option_value ("value", DEFAULT_NORM_VALUE); - - { - ProgressBar progress ("normalising tissue compartments..."); - 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()) { - 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 (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_image); i; ++i) - output_image.value() = input.value() * w(j,0); - } else { - 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++; - } - } -} - From 1f59e72ed054dd23347cef843975654a6c113ee3 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Fri, 7 Jul 2017 16:27:00 +1000 Subject: [PATCH 057/139] docs update --- docs/reference/commands/mtbin.rst | 20 +++---- docs/reference/commands/mtnormalise.rst | 71 ------------------------- docs/reference/commands_list.rst | 4 +- 3 files changed, 11 insertions(+), 84 deletions(-) delete mode 100644 docs/reference/commands/mtnormalise.rst diff --git a/docs/reference/commands/mtbin.rst b/docs/reference/commands/mtbin.rst index 899305ce12..05ebcf996d 100644 --- a/docs/reference/commands/mtbin.rst +++ b/docs/reference/commands/mtbin.rst @@ -6,7 +6,7 @@ mtbin Synopsis -------- -Please use the new mtlognorm command instead. +Multi-Tissue Bias field correction and Intensity Normalisation (WARNING: deprecated). Usage -------- @@ -15,27 +15,27 @@ Usage mtbin [ options ] input output [ 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 ----------- -WARNING: there were some major issues with this method. Please start using the new mtlognorm command instead. +WARNING: this command is deprecated and may produce inappropriate results in several cases. Please use the new mtnormalise. Options ------- -- **-mask image** +- **-mask image** define the mask to compute the normalisation within. This option is mandatory. -- **-value number** +- **-value number** specify the value to which the summed tissue compartments will be normalised to (Default: sqrt(1/(4*pi)) = 0.282094) -- **-bias image** +- **-bias image** output the estimated bias field -- **-independent** +- **-independent** intensity normalise each tissue type independently -- **-maxiter number** +- **-maxiter number** set the maximum number of iterations. Default(100). It will stop before the max iterations if convergence is detected -- **-check image** +- **-check image** check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure. However, these regions are still corrected for bias fields based on the other image data. Standard options ^^^^^^^^^^^^^^^^ @@ -60,7 +60,7 @@ Standard options -**Author:** +**Author:** David Raffelt (david.raffelt@florey.edu.au), Rami Tabbara (rami.tabbara@florey.edu.au) and Thijs Dhollander (thijs.dhollander@gmail.com) **Copyright:** Copyright (c) 2008-2017 the MRtrix3 contributors. diff --git a/docs/reference/commands/mtnormalise.rst b/docs/reference/commands/mtnormalise.rst deleted file mode 100644 index a74f2d78d9..0000000000 --- a/docs/reference/commands/mtnormalise.rst +++ /dev/null @@ -1,71 +0,0 @@ -.. _mtnormalise: - -mtnormalise -=================== - -Synopsis --------- - -Multi-tissue normalise - -Usage --------- - -:: - - mtnormalise [ 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 ------------ - -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 -^^^^^^^^^^^^^^^^ - -- **-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-2017 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 http://www.mrtrix.org/. - - diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index d17e5dfe83..ec2940ee5a 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -79,7 +79,6 @@ List of MRtrix3 commands commands/mrview commands/mtbin commands/mtlognorm - commands/mtnormalise commands/peaks2amp commands/sh2amp commands/sh2peaks @@ -188,9 +187,8 @@ List of MRtrix3 commands :ref:`mrthreshold`, "Create bitwise image by thresholding image intensity" :ref:`mrtransform`, "Apply spatial transformations to an image" :ref:`mrview`, "The MRtrix image viewer." - :ref:`mtbin`, "Please use the new mtlognorm command instead." + :ref:`mtbin`, "Multi-Tissue Bias field correction and Intensity Normalisation (WARNING: deprecated)." :ref:`mtlognorm`, "Multi-tissue informed log-domain intensity normalisation" - :ref:`mtnormalise`, "Multi-tissue normalise" :ref:`peaks2amp`, "Convert peak directions image to amplitudes" :ref:`sh2amp`, "Evaluate the amplitude of an image of spherical harmonic functions along specified directions" :ref:`sh2peaks`, "Extract the peaks of a spherical harmonic function at each voxel, by commencing a Newton search along a set of specified directions" From bcc9b9627dd2875fe9c9642d85b353281c5bf459 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Fri, 7 Jul 2017 16:38:02 +1000 Subject: [PATCH 058/139] mtlognorm --> mtnormalise --- cmd/{mtlognorm.cpp => mtnormalise.cpp} | 2 +- docs/concepts/global_intensity_normalisation.rst | 2 +- .../mt_fibre_density_cross-section.rst | 6 +++--- .../reference/commands/{mtlognorm.rst => mtnormalise.rst} | 8 ++++---- docs/reference/commands_list.rst | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) rename cmd/{mtlognorm.cpp => mtnormalise.cpp} (99%) rename docs/reference/commands/{mtlognorm.rst => mtnormalise.rst} (93%) diff --git a/cmd/mtlognorm.cpp b/cmd/mtnormalise.cpp similarity index 99% rename from cmd/mtlognorm.cpp rename to cmd/mtnormalise.cpp index d0aee0c704..031d339b3b 100644 --- a/cmd/mtlognorm.cpp +++ b/cmd/mtnormalise.cpp @@ -45,7 +45,7 @@ void usage () "accounted for and reoptimised as the intensity inhomogeneity estimation becomes " "more accurate." - + "Example usage: mtlognorm wmfod.mif wmfod_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif."; + + "Example usage: mtnormalise wmfod.mif wmfod_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif."; ARGUMENTS + Argument ("input output", "list of all input and output tissue compartment files. See example usage in the description.").type_image_in().allow_multiple(); diff --git a/docs/concepts/global_intensity_normalisation.rst b/docs/concepts/global_intensity_normalisation.rst index b1d02c3365..e2d4ce1931 100644 --- a/docs/concepts/global_intensity_normalisation.rst +++ b/docs/concepts/global_intensity_normalisation.rst @@ -12,5 +12,5 @@ In theory, a sound approach to global intensity normalisation would be to normal We have included the :ref:`dwiintensitynorm` script in MRtrix to perform an automatic global normalisation using the median white matter b=0 value. The script input requires two folders: a folder containing all DW images in the study (in .mif format) and a folder containing the corresponding whole brain mask images (with the same filename prefix). The script runs by first computing diffusion tensor Fractional Anisotropy (FA) maps, registering these to a study-specific template, then thresholding the template FA map to obtain an approximate white matter mask. The mask is then transformed back into the space of each subject image and used in the :ref:`dwinormalise` command to normalise the input DW images to have the same b=0 white matter median value. All intensity normalised data will be output in a single folder. As previously mentioned all DWI data must be bias field corrected before using :ref:`dwiintensitynorm`, for example using :code:`dwibiascorrect`. -As an alternative to the :code:`dwiintensitynorm` script, we have recently provided a new command called :ref:`mtlognorm`, which performs multi-tissue informed intensity normalisation in the log-domain. The benefit of the :ref:`mtlognorm` command is that normalisation can be performed independently on each subject, and therefore does not require a computationally expensive registration step to a group template. However, to perform multi-tissue CSD with 3 tissue types (WM, GM, CSF) currently requires DWI data with multiple b-values (this will change at some stage, when the implementation of single-shell 3-tissue CSD becomes available). +As an alternative to the :code:`dwiintensitynorm` script, we have recently provided a new command called :ref:`mtnormalise`, which performs multi-tissue informed intensity normalisation in the log-domain. The benefit of the :ref:`mtnormalise` command is that normalisation can be performed independently on each subject, and therefore does not require a computationally expensive registration step to a group template. However, to perform multi-tissue CSD with 3 tissue types (WM, GM, CSF) currently requires DWI data with multiple b-values (this will change at some stage, when the implementation of single-shell 3-tissue CSD becomes available). diff --git a/docs/fixel_based_analysis/mt_fibre_density_cross-section.rst b/docs/fixel_based_analysis/mt_fibre_density_cross-section.rst index 0bcfbff6a4..c96ca88cb1 100644 --- a/docs/fixel_based_analysis/mt_fibre_density_cross-section.rst +++ b/docs/fixel_based_analysis/mt_fibre_density_cross-section.rst @@ -34,7 +34,7 @@ Pre-processsing steps 3. Bias field correction ^^^^^^^^^^^^^^^^^^^^^^^^ -Bias field correction is important to deal with spatial intensity inhomogeneities. Even though this FBA pipeline will account for these as well (in the later :ref:`mtlognorm` step, which is furthermore crucial to correct for global intensity differences between subjects), performing bias field correction at this stage will allow for more accurate estimation of the tissue response functions as well as the individual subject brain masks. +Bias field correction is important to deal with spatial intensity inhomogeneities. Even though this FBA pipeline will account for these as well (in the later :ref:`mtnormalise` step, which is furthermore crucial to correct for global intensity differences between subjects), performing bias field correction at this stage will allow for more accurate estimation of the tissue response functions as well as the individual subject brain masks. This can be done in a single step using the :ref:`dwibiascorrect` script in MRtrix. The script uses bias field correction algorthims available in `ANTS `_ or `FSL `_. In our experience the `N4 algorithm `_ in ANTS gives far superior results. To install N4, install the `ANTS `_ package, then perform bias field correction on DW images using:: @@ -78,9 +78,9 @@ When performing analysis of AFD, Constrained Spherical Deconvolution (CSD) shoul ^^^^^^^^^^^^^^^^^^^^^^^^^^ This step performs :ref:`global intensity normalisation ` in the log-domain by scaling all tissue types with a spatially smoothly varying normalisation field:: - foreach * : mtlognorm IN/wmfod.mif IN/wmfod_norm.mif IN/gm.mif IN/gm_norm.mif IN/csf.mif IN/csf_norm.mif -mask IN/dwi_mask_upsampled.mif + foreach * : mtnormalise IN/wmfod.mif IN/wmfod_norm.mif IN/gm.mif IN/gm_norm.mif IN/csf.mif IN/csf_norm.mif -mask IN/dwi_mask_upsampled.mif -If CSD was performed with the same single set of (average) WM, GM and CSF response functions for all subjects, then the resulting output of :ref:`mtlognorm` should make the amplitudes comparable between those subjects as well. +If CSD was performed with the same single set of (average) WM, GM and CSF response functions for all subjects, then the resulting output of :ref:`mtnormalise` should make the amplitudes comparable between those subjects as well. Note that this step is crucial in the FBA pipeline, even if bias field correction was applied during the preprocessing stage, as the latter does not correct for global intensity differences between subjects. diff --git a/docs/reference/commands/mtlognorm.rst b/docs/reference/commands/mtnormalise.rst similarity index 93% rename from docs/reference/commands/mtlognorm.rst rename to docs/reference/commands/mtnormalise.rst index cb7e8a19d9..e98bc1bb9f 100644 --- a/docs/reference/commands/mtlognorm.rst +++ b/docs/reference/commands/mtnormalise.rst @@ -1,6 +1,6 @@ -.. _mtlognorm: +.. _mtnormalise: -mtlognorm +mtnormalise =================== Synopsis @@ -13,7 +13,7 @@ Usage :: - mtlognorm [ options ] input output [ input output ... ] + mtnormalise [ options ] input output [ input output ... ] - *input output*: list of all input and output tissue compartment files. See example usage in the description. @@ -24,7 +24,7 @@ This command inputs any number of tissue components (e.g. from multi-tissue CSD) The -mask option is mandatory and is optimally provided with a brain mask (such as the one obtained from dwi2mask earlier in the processing pipeline). Outlier areas with exceptionally low or high combined tissue contributions are accounted for and reoptimised as the intensity inhomogeneity estimation becomes more accurate. -Example usage: mtlognorm wmfod.mif wmfod_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif. +Example usage: mtnormalise wmfod.mif wmfod_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif. Options ------- diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index ec2940ee5a..334465ea3a 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -78,7 +78,7 @@ List of MRtrix3 commands commands/mrtransform commands/mrview commands/mtbin - commands/mtlognorm + commands/mtnormalise commands/peaks2amp commands/sh2amp commands/sh2peaks @@ -188,7 +188,7 @@ List of MRtrix3 commands :ref:`mrtransform`, "Apply spatial transformations to an image" :ref:`mrview`, "The MRtrix image viewer." :ref:`mtbin`, "Multi-Tissue Bias field correction and Intensity Normalisation (WARNING: deprecated)." - :ref:`mtlognorm`, "Multi-tissue informed log-domain intensity normalisation" + :ref:`mtnormalise`, "Multi-tissue informed log-domain intensity normalisation" :ref:`peaks2amp`, "Convert peak directions image to amplitudes" :ref:`sh2amp`, "Evaluate the amplitude of an image of spherical harmonic functions along specified directions" :ref:`sh2peaks`, "Extract the peaks of a spherical harmonic function at each voxel, by commencing a Newton search along a set of specified directions" From 9866913d476a415d6a4d30b3c9cdceb6deabdfd0 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 7 Jul 2017 17:45:48 +1000 Subject: [PATCH 059/139] fixelcfestats: Add doc information regarding output when using -mask option --- cmd/fixelcfestats.cpp | 7 +++++++ docs/reference/commands/fixelcfestats.rst | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index 800c8a6bdd..fbcd485a22 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -55,6 +55,13 @@ void usage () SYNOPSIS = "Fixel-based analysis using connectivity-based fixel enhancement and non-parametric permutation testing"; + DESCRIPTION + + "Note that if the -mask option is used, the output fixel directory will still contain the same set of fixels as that " + "present in the input fixel template, in order to retain fixel correspondence. However a consequence of this is that " + "all fixels in the template will be initialy visible when the output fixel directory is loaded in mrview. Those fixels " + "outside the processing mask will immediately disappear from view as soon as any data-file-based fixel colouring or " + "thresholding is applied."; + REFERENCES + "Raffelt, D.; Smith, RE.; Ridgway, GR.; Tournier, JD.; Vaughan, DN.; Rose, S.; Henderson, R.; Connelly, A." // Internal "Connectivity-based fixel enhancement: Whole-brain statistical analysis of diffusion MRI measures in the presence of crossing fibres. \n" diff --git a/docs/reference/commands/fixelcfestats.rst b/docs/reference/commands/fixelcfestats.rst index da3d415250..7e8655a4bd 100644 --- a/docs/reference/commands/fixelcfestats.rst +++ b/docs/reference/commands/fixelcfestats.rst @@ -22,6 +22,11 @@ Usage - *tracks*: the tracks used to determine fixel-fixel connectivity - *out_fixel_directory*: the output directory where results will be saved. Will be created if it does not exist +Description +----------- + +Note that if the -mask option is used, the output fixel directory will still contain the same set of fixels as that present in the input fixel template, in order to retain fixel correspondence. However a consequence of this is that all fixels in the template will be initialy visible when the output fixel directory is loaded in mrview. Those fixels outside the processing mask will immediately disappear from view as soon as any data-file-based fixel colouring or thresholding is applied. + Options ------- From 577ceb2b796c57768d63162897f2c1f5b940d5fa Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sat, 8 Jul 2017 16:37:15 +1000 Subject: [PATCH 060/139] mtnormalise: Various tweaks and cleanups --- cmd/mtnormalise.cpp | 104 ++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 61 deletions(-) diff --git a/cmd/mtnormalise.cpp b/cmd/mtnormalise.cpp index 031d339b3b..f1ef61970b 100644 --- a/cmd/mtnormalise.cpp +++ b/cmd/mtnormalise.cpp @@ -64,12 +64,12 @@ void usage () "This mask excludes regions identified as outliers by the optimisation process.") + Argument ("image").type_image_out () - + Option ("value", "specify the reference value to which the summed tissue compartments will be normalised. " + + Option ("value", "specify the reference (positive) value to which the summed tissue compartments will be normalised. " "(default: " + str(DEFAULT_NORM_VALUE, 6) + ", SH DC term for unit angular integral)") - + Argument ("number").type_float (); + + Argument ("number").type_float (std::numeric_limits::min()); } -const int n_basis_vecs (20); +constexpr int n_basis_vecs = 20; FORCE_INLINE Eigen::MatrixXd basis_function (const Eigen::Vector3 pos) { @@ -119,7 +119,7 @@ void run () if (argument.size() % 2) throw Exception ("The number of arguments must be even, provided as pairs of each input and its corresponding output file."); - ProgressBar progress ("performing log-domain intensity normalisation..."); + ProgressBar progress ("performing log-domain intensity normalisation"); using ImageType = Image; using MaskType = Image; @@ -144,8 +144,7 @@ void run () Header h_image4d (image); h_image4d.ndim() = 4; - Adapter::Replicate image4d (image, h_image4d); - input_images.emplace_back (image4d); + input_images.emplace_back (image, h_image4d); if (i > 0) check_dimensions (input_images[0], input_images[i / 2], 0, 3); @@ -153,8 +152,8 @@ void run () if (Path::exists (argument[i + 1]) && !App::overwrite_files) throw Exception ("Output file \"" + argument[i] + "\" already exists. (use -force option to force overwrite)"); - output_headers.emplace_back (h_image4d); - output_filenames.emplace_back (argument[i + 1]); + output_headers.push_back (std::move (h_image4d)); + output_filenames.push_back (argument[i + 1]); } const size_t n_tissue_types = input_images.size(); @@ -166,19 +165,20 @@ void run () auto opt = get_options ("mask"); auto orig_mask = MaskType::open (opt[0][0]); - auto initial_mask = MaskType::scratch (orig_mask); - auto mask = MaskType::scratch (orig_mask); - auto prev_mask = MaskType::scratch (orig_mask); - - auto summed = ImageType::scratch (header_3D); - for (size_t j = 0; j < input_images.size(); ++j) { - for (auto i = Loop (0, 3) (summed, input_images[j]); i; ++i) - summed.value() += input_images[j].value(); - progress++; + auto initial_mask = MaskType::scratch (orig_mask, "Initial processing mask"); + auto mask = MaskType::scratch (orig_mask, "Processing mask"); + auto prev_mask = MaskType::scratch (orig_mask, "Previous processing mask"); + + { + auto summed = ImageType::scratch (header_3D, "Summed tissue volumes"); + for (size_t j = 0; j < input_images.size(); ++j) { + for (auto i = Loop (0, 3) (summed, input_images[j]); i; ++i) + summed.value() += input_images[j].value(); + progress++; + } + refine_mask (summed, orig_mask, initial_mask); } - refine_mask (summed, orig_mask, initial_mask); - threaded_copy (initial_mask, mask); @@ -205,31 +205,23 @@ void run () throw Exception ("Mask contains no valid voxels."); - // Load global normalisation factor const float normalisation_value = get_option_value ("value", DEFAULT_NORM_VALUE); - - if (normalisation_value <= 0.f) - throw Exception ("Normalisation reference value (-value option) must be strictly positive."); - const float log_norm_value = std::log (normalisation_value); - const size_t max_iter = get_option_value ("niter", DEFAULT_MAIN_ITER_VALUE); - const size_t max_inner_iter = DEFAULT_INNER_MAXITER_VALUE; // Initialise normalisation fields in both image and log domain - Eigen::MatrixXd norm_field_weights (n_basis_vecs, 0); + Eigen::MatrixXd norm_field_weights; - auto norm_field_image = ImageType::scratch (header_3D); - auto norm_field_log = ImageType::scratch (header_3D); + auto norm_field_image = ImageType::scratch (header_3D, "Normalisation field (intensity)"); + auto norm_field_log = ImageType::scratch (header_3D, "Normalisation field (log-space)"); for (auto i = Loop(norm_field_log) (norm_field_image, norm_field_log); i; ++i) { norm_field_image.value() = 1.f; norm_field_log.value() = 0.f; } - Eigen::VectorXd balance_factors (n_tissue_types); - balance_factors.fill(1); + Eigen::VectorXd balance_factors (Eigen::VectorXd::Ones (n_tissue_types)); size_t iter = 1; @@ -239,32 +231,33 @@ void run () // tissue balance factors auto outlier_rejection = [&](float outlier_range) { - auto summed_log = ImageType::scratch (header_3D); + auto summed_log = ImageType::scratch (header_3D, "Log of summed tissue volumes"); for (size_t j = 0; j < n_tissue_types; ++j) { for (auto i = Loop (0, 3) (summed_log, combined_tissue, norm_field_image); i; ++i) { combined_tissue.index(3) = j; summed_log.value() += balance_factors(j) * combined_tissue.value() / norm_field_image.value(); } - summed_log.value() = std::log(summed_log.value()); } threaded_copy (initial_mask, mask); vector summed_log_values; + summed_log_values.reserve (num_voxels); for (auto i = Loop (0, 3) (mask, summed_log); i; ++i) { if (mask.value()) - summed_log_values.emplace_back (summed_log.value()); + summed_log_values.push_back (summed_log.value()); } + // Shouldn't be required...? num_voxels = summed_log_values.size(); - std::sort (summed_log_values.begin(), summed_log_values.end()); - float lower_quartile = summed_log_values[std::round ((float)num_voxels * 0.25)]; - float upper_quartile = summed_log_values[std::round ((float)num_voxels * 0.75)]; - float upper_outlier_threshold = upper_quartile + outlier_range * (upper_quartile - lower_quartile); - float lower_outlier_threshold = lower_quartile - outlier_range * (upper_quartile - lower_quartile); - + const auto lower_quartile = summed_log_values.begin() + std::round ((float)num_voxels * 0.25f); + std::nth_element (summed_log_values.begin(), lower_quartile, summed_log_values.end()); + const auto upper_quartile = summed_log_values.begin() + std::round ((float)num_voxels * 0.75f); + std::nth_element (lower_quartile, upper_quartile, summed_log_values.end()); + const float lower_outlier_threshold = *lower_quartile - outlier_range * (*upper_quartile - *lower_quartile); + const float upper_outlier_threshold = *upper_quartile + outlier_range * (*upper_quartile - *lower_quartile); for (auto i = Loop (0, 3) (mask, summed_log); i; ++i) { if (mask.value()) { @@ -286,7 +279,7 @@ void run () while (iter <= max_iter) { - INFO ("iteration: " + str(iter)); + DEBUG ("iteration: " + str(iter)); // Iteratively compute tissue balance factors // with outlier rejection @@ -295,14 +288,13 @@ void run () while (!balance_converged && norm_iter <= max_inner_iter) { - INFO ("norm iteration: " + str(norm_iter)); + DEBUG ("norm iteration: " + str(norm_iter)); if (n_tissue_types > 1) { // Solve for tissue balance factors Eigen::MatrixXd X (num_voxels, n_tissue_types); - Eigen::VectorXd y (num_voxels); - y.fill (1); + Eigen::VectorXd y (Eigen::VectorXd::Ones (num_voxels)); uint32_t index = 0; for (auto i = Loop (0, 3) (mask, combined_tissue, norm_field_image); i; ++i) { @@ -318,7 +310,7 @@ void run () balance_factors = X.colPivHouseholderQr().solve(y); // Ensure our balance factors satisfy the condition that sum(log(balance_factors)) = 0 - double log_sum = 0.f; + double log_sum = 0.0; for (size_t j = 0; j < n_tissue_types; ++j) { if (balance_factors(j) <= 0.0) throw Exception ("Non-positive tissue balance factor was computed." @@ -329,7 +321,7 @@ void run () balance_factors /= std::exp (log_sum / n_tissue_types); } - INFO ("Balance factors: " + str(balance_factors.transpose())); + DEBUG ("Balance factors: " + str(balance_factors.transpose())); // Perform outlier rejection on log-domain of summed images outlier_rejection(1.5f); @@ -352,7 +344,6 @@ void run () // Solve for normalisation field weights in the log domain Transform transform (mask); Eigen::MatrixXd norm_field_basis (num_voxels, n_basis_vecs); - Eigen::MatrixXd X (num_voxels, n_tissue_types); Eigen::VectorXd y (num_voxels); uint32_t index = 0; for (auto i = Loop (0, 3) (mask, combined_tissue); i; ++i) { @@ -374,14 +365,14 @@ void run () // Generate normalisation field in the log domain for (auto i = Loop (0, 3) (norm_field_log); i; ++i) { - Eigen::Vector3 vox (norm_field_log.index(0), norm_field_log.index(1), norm_field_log.index(2)); - Eigen::Vector3 pos = transform.voxel2scanner * vox; - norm_field_log.value() = basis_function (pos).col(0).dot (norm_field_weights.col(0)); + Eigen::Vector3 vox (norm_field_log.index(0), norm_field_log.index(1), norm_field_log.index(2)); + Eigen::Vector3 pos = transform.voxel2scanner * vox; + norm_field_log.value() = basis_function (pos).col(0).dot (norm_field_weights.col(0)); } // Generate normalisation field in the image domain for (auto i = Loop (0, 3) (norm_field_log, norm_field_image); i; ++i) - norm_field_image.value () = std::exp(norm_field_log.value()); + norm_field_image.value () = std::exp(norm_field_log.value()); progress++; iter++; @@ -402,17 +393,8 @@ void run () progress++; - // Generate output headers - 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; - } - // Compute log-norm scale parameter (geometric mean of normalisation field in outlier-free mask). - float lognorm_scale { 0.f }; + float lognorm_scale (0.f); if (num_voxels) { for (auto i = Loop (0,3) (mask, norm_field_log); i; ++i) { if (mask.value ()) From 9bdd659fffa6160d141ab0ae1d958764480328e1 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sat, 8 Jul 2017 21:28:02 +1000 Subject: [PATCH 061/139] mtnormalise: Fix efficient quartile algorithm Because the lower quartile iterator was used as the starting position for the second nth_element() call, it was possible for the second invocation to change the value underlying that iterator before it was read. --- cmd/mtnormalise.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cmd/mtnormalise.cpp b/cmd/mtnormalise.cpp index f1ef61970b..dc732a03dc 100644 --- a/cmd/mtnormalise.cpp +++ b/cmd/mtnormalise.cpp @@ -252,12 +252,14 @@ void run () // Shouldn't be required...? num_voxels = summed_log_values.size(); - const auto lower_quartile = summed_log_values.begin() + std::round ((float)num_voxels * 0.25f); - std::nth_element (summed_log_values.begin(), lower_quartile, summed_log_values.end()); - const auto upper_quartile = summed_log_values.begin() + std::round ((float)num_voxels * 0.75f); - std::nth_element (lower_quartile, upper_quartile, summed_log_values.end()); - const float lower_outlier_threshold = *lower_quartile - outlier_range * (*upper_quartile - *lower_quartile); - const float upper_outlier_threshold = *upper_quartile + outlier_range * (*upper_quartile - *lower_quartile); + const auto lower_quartile_it = summed_log_values.begin() + std::round ((float)num_voxels * 0.25f); + std::nth_element (summed_log_values.begin(), lower_quartile_it, summed_log_values.end()); + const float lower_quartile = *lower_quartile_it; + const auto upper_quartile_it = summed_log_values.begin() + std::round ((float)num_voxels * 0.75f); + std::nth_element (lower_quartile_it, upper_quartile_it, summed_log_values.end()); + const float upper_quartile = *upper_quartile_it; + const float lower_outlier_threshold = lower_quartile - outlier_range * (upper_quartile - lower_quartile); + const float upper_outlier_threshold = upper_quartile + outlier_range * (upper_quartile - lower_quartile); for (auto i = Loop (0, 3) (mask, summed_log); i; ++i) { if (mask.value()) { From 98dfb74f94c6b16bb6763ea6a95706c4d6f7e861 Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Mon, 10 Jul 2017 16:36:27 +1000 Subject: [PATCH 062/139] mtnormalise: Adding ability to set order of polynomial function to fit field --- cmd/mtnormalise.cpp | 162 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 127 insertions(+), 35 deletions(-) diff --git a/cmd/mtnormalise.cpp b/cmd/mtnormalise.cpp index dc732a03dc..f7c3d933cb 100644 --- a/cmd/mtnormalise.cpp +++ b/cmd/mtnormalise.cpp @@ -26,6 +26,9 @@ using namespace App; #define DEFAULT_NORM_VALUE 0.28209479177 #define DEFAULT_MAIN_ITER_VALUE 15 #define DEFAULT_INNER_MAXITER_VALUE 7 +#define DEFAULT_POLY_ORDER 3 + +const char* poly_order_choices[] = { "0", "1", "2", "3", nullptr }; void usage () { @@ -66,40 +69,103 @@ void usage () + Option ("value", "specify the reference (positive) value to which the summed tissue compartments will be normalised. " "(default: " + str(DEFAULT_NORM_VALUE, 6) + ", SH DC term for unit angular integral)") - + Argument ("number").type_float (std::numeric_limits::min()); -} + + Argument ("number").type_float (std::numeric_limits::min()) -constexpr int n_basis_vecs = 20; - - -FORCE_INLINE Eigen::MatrixXd basis_function (const Eigen::Vector3 pos) { - 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; - basis(3) = z; - basis(4) = x * x; - basis(5) = y * y; - basis(6) = z * z; - basis(7) = x * y; - basis(8) = x * z; - basis(9) = y * z; - basis(10) = x * x * x; - basis(11) = y * y * y; - basis(12) = z * z * z; - basis(13) = x * x * y; - basis(14) = x * x * z; - basis(15) = y * y * x; - basis(16) = y * y * z; - basis(17) = z * z * x; - basis(18) = z * z * y; - basis(19) = x * y * z; - return basis; + + Option ("poly_order", "the order of the polynomial function used to fit the normalisation field. (default: " + str(DEFAULT_POLY_ORDER) + ")") + + Argument ("order").type_choice (poly_order_choices); } + +template +struct PolyBasisFunction { + + const int n_basis_vecs = 20; + + FORCE_INLINE Eigen::MatrixXd operator () (const Eigen::Vector3& pos) { + 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; + basis(3) = z; + basis(4) = x * x; + basis(5) = y * y; + basis(6) = z * z; + basis(7) = x * y; + basis(8) = x * z; + basis(9) = y * z; + basis(10) = x * x * x; + basis(11) = y * y * y; + basis(12) = z * z * z; + basis(13) = x * x * y; + basis(14) = x * x * z; + basis(15) = y * y * x; + basis(16) = y * y * z; + basis(17) = z * z * x; + basis(18) = z * z * y; + basis(19) = x * y * z; + return basis; + } +}; + + +template <> +struct PolyBasisFunction<0> { + const int n_basis_vecs = 1; + + PolyBasisFunction() {} + FORCE_INLINE Eigen::MatrixXd operator () (const Eigen::Vector3&) { + Eigen::MatrixXd basis(n_basis_vecs, 1); + basis(0) = 1.0; + return basis; + } +}; + + +template <> +struct PolyBasisFunction<1> { + const int n_basis_vecs = 4; + + FORCE_INLINE Eigen::MatrixXd operator () (const Eigen::Vector3& pos) { + 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; + basis(3) = z; + return basis; + } +}; + + +template <> +struct PolyBasisFunction<2> { + const int n_basis_vecs = 10; + + FORCE_INLINE Eigen::MatrixXd operator () (const Eigen::Vector3& pos) { + 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; + basis(3) = z; + basis(4) = x * x; + basis(5) = y * y; + basis(6) = z * z; + basis(7) = x * y; + basis(8) = x * z; + basis(9) = y * z; + return basis; + } +}; + + // Removes non-physical voxels from the mask FORCE_INLINE void refine_mask (Image& summed, Image& initial_mask, @@ -114,12 +180,41 @@ FORCE_INLINE void refine_mask (Image& summed, } +template void run_primitive (); + void run () { if (argument.size() % 2) throw Exception ("The number of arguments must be even, provided as pairs of each input and its corresponding output file."); + // Get poly-order of basis function + auto opt = get_options ("poly_order"); + if (opt.size ()) { + const int order = int(opt[0][0]); + switch (order) { + case 0: + run_primitive<0> (); + break; + case 1: + run_primitive<1> (); + break; + case 2: + run_primitive<2> (); + break; + default: + run_primitive (); + break; + } + } else + run_primitive (); +} + + +template +void run_primitive () { + ProgressBar progress ("performing log-domain intensity normalisation"); + PolyBasisFunction basis_function; using ImageType = Image; using MaskType = Image; @@ -128,7 +223,6 @@ void run () vector
output_headers; vector output_filenames; - // Open input images and prepare output image headers for (size_t i = 0; i < argument.size(); i += 2) { progress++; @@ -158,7 +252,6 @@ void run () const size_t n_tissue_types = input_images.size(); - // Load the mask and refine the initial mask to exclude non-positive summed tissue components Header header_3D (input_images[0]); header_3D.ndim() = 3; @@ -249,7 +342,6 @@ void run () summed_log_values.push_back (summed_log.value()); } - // Shouldn't be required...? num_voxels = summed_log_values.size(); const auto lower_quartile_it = summed_log_values.begin() + std::round ((float)num_voxels * 0.25f); @@ -345,7 +437,7 @@ void run () // Solve for normalisation field weights in the log domain Transform transform (mask); - Eigen::MatrixXd norm_field_basis (num_voxels, n_basis_vecs); + Eigen::MatrixXd norm_field_basis (num_voxels, basis_function.n_basis_vecs); Eigen::VectorXd y (num_voxels); uint32_t index = 0; for (auto i = Loop (0, 3) (mask, combined_tissue); i; ++i) { From 7590bcd64e673bb1f7f16b2dd3e283a116e99cf7 Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Mon, 10 Jul 2017 17:08:50 +1000 Subject: [PATCH 063/139] mtnormalise: Separating progress bar to distinguish between loading input, intensity normalisation computations and writing output --- cmd/mtnormalise.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/cmd/mtnormalise.cpp b/cmd/mtnormalise.cpp index f7c3d933cb..89185f5b13 100644 --- a/cmd/mtnormalise.cpp +++ b/cmd/mtnormalise.cpp @@ -213,7 +213,6 @@ void run () template void run_primitive () { - ProgressBar progress ("performing log-domain intensity normalisation"); PolyBasisFunction basis_function; using ImageType = Image; @@ -223,9 +222,11 @@ void run_primitive () { vector
output_headers; vector output_filenames; + ProgressBar input_progress ("loading input images"); + // Open input images and prepare output image headers for (size_t i = 0; i < argument.size(); i += 2) { - progress++; + input_progress++; auto image = ImageType::open (argument[i]); @@ -265,9 +266,10 @@ void run_primitive () { { auto summed = ImageType::scratch (header_3D, "Summed tissue volumes"); for (size_t j = 0; j < input_images.size(); ++j) { + input_progress++; + for (auto i = Loop (0, 3) (summed, input_images[j]); i; ++i) summed.value() += input_images[j].value(); - progress++; } refine_mask (summed, orig_mask, initial_mask); } @@ -282,6 +284,8 @@ void run_primitive () { auto combined_tissue = ImageType::scratch (h_combined_tissue, "Tissue components"); for (size_t i = 0; i < n_tissue_types; ++i) { + input_progress++; + combined_tissue.index (3) = i; for (auto l = Loop (0, 3) (combined_tissue, input_images[i]); l; ++l) { combined_tissue.value () = std::max(input_images[i].value (), 0.f); @@ -366,6 +370,9 @@ void run_primitive () { display (mask); }; + input_progress.done (); + ProgressBar progress ("performing log-domain intensity normalisation"); + // Perform an initial outlier rejection prior to the first iteration outlier_rejection (3.f); @@ -374,6 +381,7 @@ void run_primitive () { while (iter <= max_iter) { DEBUG ("iteration: " + str(iter)); + progress++; // Iteratively compute tissue balance factors // with outlier rejection @@ -383,6 +391,7 @@ void run_primitive () { while (!balance_converged && norm_iter <= max_inner_iter) { DEBUG ("norm iteration: " + str(norm_iter)); + progress++; if (n_tissue_types > 1) { @@ -467,25 +476,24 @@ void run_primitive () { // Generate normalisation field in the image domain for (auto i = Loop (0, 3) (norm_field_log, norm_field_image); i; ++i) norm_field_image.value () = std::exp(norm_field_log.value()); - - progress++; iter++; } + progress.done(); + + ProgressBar output_progress("writing output images"); + opt = get_options ("check_norm"); if (opt.size()) { auto norm_field_output = ImageType::create (opt[0][0], header_3D); threaded_copy (norm_field_image, norm_field_output); } - progress++; opt = get_options ("check_mask"); if (opt.size()) { auto mask_output = ImageType::create (opt[0][0], mask); threaded_copy (mask, mask_output); } - progress++; - // Compute log-norm scale parameter (geometric mean of normalisation field in outlier-free mask). float lognorm_scale (0.f); @@ -500,6 +508,8 @@ void run_primitive () { for (size_t j = 0; j < output_filenames.size(); ++j) { + output_progress++; + output_headers[j].keyval()["lognorm_scale"] = str(lognorm_scale); auto output_image = ImageType::create (output_filenames[j], output_headers[j]); const size_t n_vols = input_images[j].size(3); From 321c607c5ca65fbeef9f4cf06dab14078ee71473 Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Mon, 10 Jul 2017 17:27:36 +1000 Subject: [PATCH 064/139] mtnormalise: Adding missing memalign macros --- cmd/mtnormalise.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/mtnormalise.cpp b/cmd/mtnormalise.cpp index 89185f5b13..b05930230a 100644 --- a/cmd/mtnormalise.cpp +++ b/cmd/mtnormalise.cpp @@ -77,7 +77,7 @@ void usage () template -struct PolyBasisFunction { +struct PolyBasisFunction { MEMALIGN (PolyBasisFunction) const int n_basis_vecs = 20; @@ -112,7 +112,7 @@ struct PolyBasisFunction { template <> -struct PolyBasisFunction<0> { +struct PolyBasisFunction<0> { MEMALIGN (PolyBasisFunction<0>) const int n_basis_vecs = 1; PolyBasisFunction() {} @@ -125,7 +125,7 @@ struct PolyBasisFunction<0> { template <> -struct PolyBasisFunction<1> { +struct PolyBasisFunction<1> { MEMALIGN (PolyBasisFunction<1>) const int n_basis_vecs = 4; FORCE_INLINE Eigen::MatrixXd operator () (const Eigen::Vector3& pos) { @@ -143,7 +143,7 @@ struct PolyBasisFunction<1> { template <> -struct PolyBasisFunction<2> { +struct PolyBasisFunction<2> { MEMALIGN (PolyBasisFunction<2>) const int n_basis_vecs = 10; FORCE_INLINE Eigen::MatrixXd operator () (const Eigen::Vector3& pos) { From 01c0a906f2e6de122e2c2eda4b4f9492069be007 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 11 Jul 2017 13:54:53 +1000 Subject: [PATCH 065/139] docs --- docs/reference/commands/mtnormalise.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/reference/commands/mtnormalise.rst b/docs/reference/commands/mtnormalise.rst index e98bc1bb9f..940dbd68c5 100644 --- a/docs/reference/commands/mtnormalise.rst +++ b/docs/reference/commands/mtnormalise.rst @@ -37,7 +37,9 @@ Options - **-check_mask image** output the final mask used to compute the normalisation. This mask excludes regions identified as outliers by the optimisation process. -- **-value number** specify the reference value to which the summed tissue compartments will be normalised. (default: 0.282095, SH DC term for unit angular integral) +- **-value number** specify the reference (positive) value to which the summed tissue compartments will be normalised. (default: 0.282095, SH DC term for unit angular integral) + +- **-poly_order order** the order of the polynomial function used to fit the normalisation field. (default: 3) Standard options ^^^^^^^^^^^^^^^^ From d0023d45332f8e87db7431afa2c88e4e94505058 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 11 Jul 2017 14:35:48 +1000 Subject: [PATCH 066/139] mtnormalise interface updates --- cmd/mtnormalise.cpp | 19 ++++++++++--------- docs/reference/commands/mtnormalise.rst | 6 +++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/cmd/mtnormalise.cpp b/cmd/mtnormalise.cpp index b05930230a..000ea35736 100644 --- a/cmd/mtnormalise.cpp +++ b/cmd/mtnormalise.cpp @@ -57,6 +57,9 @@ void usage () + Option ("mask", "the mask defines the data used to compute the intensity normalisation. This option is mandatory.").required () + Argument ("image").type_image_in () + + Option ("order", "the maximum order of the polynomial basis used to fit the normalisation field in the log-domain. An order of 0 is equivalent to not allowing spatial variance of the intensity normalisation factor. (default: " + str(DEFAULT_POLY_ORDER) + ")") + + Argument ("number").type_choice (poly_order_choices) + + Option ("niter", "set the number of iterations. (default: " + str(DEFAULT_MAIN_ITER_VALUE) + ")") + Argument ("number").type_integer() @@ -67,12 +70,10 @@ void usage () "This mask excludes regions identified as outliers by the optimisation process.") + Argument ("image").type_image_out () - + Option ("value", "specify the reference (positive) value to which the summed tissue compartments will be normalised. " + + Option ("value", "specify the (positive) reference value to which the summed tissue compartments will be normalised. " "(default: " + str(DEFAULT_NORM_VALUE, 6) + ", SH DC term for unit angular integral)") - + Argument ("number").type_float (std::numeric_limits::min()) + + Argument ("number").type_float (std::numeric_limits::min()); - + Option ("poly_order", "the order of the polynomial function used to fit the normalisation field. (default: " + str(DEFAULT_POLY_ORDER) + ")") - + Argument ("order").type_choice (poly_order_choices); } @@ -188,7 +189,7 @@ void run () throw Exception ("The number of arguments must be even, provided as pairs of each input and its corresponding output file."); // Get poly-order of basis function - auto opt = get_options ("poly_order"); + auto opt = get_options ("order"); if (opt.size ()) { const int order = int(opt[0][0]); switch (order) { @@ -311,7 +312,7 @@ void run_primitive () { Eigen::MatrixXd norm_field_weights; auto norm_field_image = ImageType::scratch (header_3D, "Normalisation field (intensity)"); - auto norm_field_log = ImageType::scratch (header_3D, "Normalisation field (log-space)"); + auto norm_field_log = ImageType::scratch (header_3D, "Normalisation field (log-domain)"); for (auto i = Loop(norm_field_log) (norm_field_image, norm_field_log); i; ++i) { norm_field_image.value() = 1.f; @@ -380,7 +381,7 @@ void run_primitive () { while (iter <= max_iter) { - DEBUG ("iteration: " + str(iter)); + INFO ("iteration: " + str(iter)); progress++; // Iteratively compute tissue balance factors @@ -390,7 +391,7 @@ void run_primitive () { while (!balance_converged && norm_iter <= max_inner_iter) { - DEBUG ("norm iteration: " + str(norm_iter)); + INFO ("norm iteration: " + str(norm_iter)); progress++; if (n_tissue_types > 1) { @@ -424,7 +425,7 @@ void run_primitive () { balance_factors /= std::exp (log_sum / n_tissue_types); } - DEBUG ("Balance factors: " + str(balance_factors.transpose())); + INFO ("Balance factors: " + str(balance_factors.transpose())); // Perform outlier rejection on log-domain of summed images outlier_rejection(1.5f); diff --git a/docs/reference/commands/mtnormalise.rst b/docs/reference/commands/mtnormalise.rst index 940dbd68c5..4bef89ae0c 100644 --- a/docs/reference/commands/mtnormalise.rst +++ b/docs/reference/commands/mtnormalise.rst @@ -31,15 +31,15 @@ Options - **-mask image** the mask defines the data used to compute the intensity normalisation. This option is mandatory. +- **-order number** the maximum order of the polynomial basis used to fit the normalisation field in the log-domain. An order of 0 is equivalent to not allowing spatial variance of the intensity normalisation factor. (default: 3) + - **-niter number** set the number of iterations. (default: 15) - **-check_norm image** output the final estimated spatially varying intensity level that is used for normalisation. - **-check_mask image** output the final mask used to compute the normalisation. This mask excludes regions identified as outliers by the optimisation process. -- **-value number** specify the reference (positive) value to which the summed tissue compartments will be normalised. (default: 0.282095, SH DC term for unit angular integral) - -- **-poly_order order** the order of the polynomial function used to fit the normalisation field. (default: 3) +- **-value number** specify the (positive) reference value to which the summed tissue compartments will be normalised. (default: 0.282095, SH DC term for unit angular integral) Standard options ^^^^^^^^^^^^^^^^ From 6e83120938efa9530a85942ab2d1e630b4d72b35 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 11 Jul 2017 15:54:43 +1000 Subject: [PATCH 067/139] mtnormalise info output tweak --- cmd/mtnormalise.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/cmd/mtnormalise.cpp b/cmd/mtnormalise.cpp index 000ea35736..b99f8e41d9 100644 --- a/cmd/mtnormalise.cpp +++ b/cmd/mtnormalise.cpp @@ -381,17 +381,16 @@ void run_primitive () { while (iter <= max_iter) { - INFO ("iteration: " + str(iter)); + INFO ("Iteration: " + str(iter)); progress++; - // Iteratively compute tissue balance factors - // with outlier rejection - size_t norm_iter = 1; + // Iteratively compute tissue balance factors with outlier rejection + size_t balance_iter = 1; bool balance_converged = false; - while (!balance_converged && norm_iter <= max_inner_iter) { + while (!balance_converged && balance_iter <= max_inner_iter) { - INFO ("norm iteration: " + str(norm_iter)); + DEBUG ("Balance and outlier rejection iteration " + str(balance_iter) + " starts."); progress++; if (n_tissue_types > 1) { @@ -425,7 +424,7 @@ void run_primitive () { balance_factors /= std::exp (log_sum / n_tissue_types); } - INFO ("Balance factors: " + str(balance_factors.transpose())); + INFO ("Balance factors (" + str(balance_iter) + "): " + str(balance_factors.transpose())); // Perform outlier rejection on log-domain of summed images outlier_rejection(1.5f); @@ -441,7 +440,7 @@ void run_primitive () { threaded_copy (mask, prev_mask); - norm_iter++; + balance_iter++; } From 1179f087eb6741989d0bc85d846e052c3c040849 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Tue, 11 Jul 2017 16:05:01 +0100 Subject: [PATCH 068/139] dirgen: fix repulstion law to default to E = 1/r --- cmd/dirgen.cpp | 60 +++++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/cmd/dirgen.cpp b/cmd/dirgen.cpp index a1911766ba..f3f0a87825 100644 --- a/cmd/dirgen.cpp +++ b/cmd/dirgen.cpp @@ -19,7 +19,7 @@ #include "math/check_gradient.h" #include "dwi/directions/file.h" -#define DEFAULT_POWER 2 +#define DEFAULT_POWER 1 #define DEFAULT_NITER 10000 @@ -29,33 +29,41 @@ using namespace App; void usage () { -AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; + AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; -SYNOPSIS = "Generate a set of uniformly distributed directions using a bipolar electrostatic repulsion model"; + SYNOPSIS = "Generate a set of uniformly distributed directions using a bipolar electrostatic repulsion model"; -REFERENCES - + "Jones, D.; Horsfield, M. & Simmons, A. " - "Optimal strategies for measuring diffusion in anisotropic systems by magnetic resonance imaging. " - "Magnetic Resonance in Medicine, 1999, 42: 515-525" - - + "Papadakis, N. G.; Murrills, C. D.; Hall, L. D.; Huang, C. L.-H. & Adrian Carpenter, T. " - "Minimal gradient encoding for robust estimation of diffusion anisotropy. " - "Magnetic Resonance Imaging, 2000, 18: 671-679"; + DESCRIPTION + + "Directions are distributed by analogy to an electrostatic repulsion system, with each direction " + "corresponding to a single electrostatic charge (for -unipolar), or a pair of diametrically opposed charges " + "for the default bipolar case). The energy of the system is determined based on the Coulomb repulstion, " + "which assumed the form 1/r^power, where r is the distance between any pair of charges, and p is the power " + "assumed for the repulsion law (default: 1). The minimum energy state is obtained by gradient descent."; -ARGUMENTS - + Argument ("ndir", "the number of directions to generate.").type_integer (6, std::numeric_limits::max()) - + Argument ("dirs", "the text file to write the directions to, as [ az el ] pairs.").type_file_out(); -OPTIONS - + Option ("power", "specify exponent to use for repulsion power law (default: " + str(DEFAULT_POWER) + "). This must be a power of 2 (i.e. 2, 4, 8, 16, ...).") - + Argument ("exp").type_integer (2, std::numeric_limits::max()) + REFERENCES + + "Jones, D.; Horsfield, M. & Simmons, A. " + "Optimal strategies for measuring diffusion in anisotropic systems by magnetic resonance imaging. " + "Magnetic Resonance in Medicine, 1999, 42: 515-525" - + Option ("niter", "specify the maximum number of iterations to perform (default: " + str(DEFAULT_NITER) + ").") - + Argument ("num").type_integer (1, std::numeric_limits::max()) + + "Papadakis, N. G.; Murrills, C. D.; Hall, L. D.; Huang, C. L.-H. & Adrian Carpenter, T. " + "Minimal gradient encoding for robust estimation of diffusion anisotropy. " + "Magnetic Resonance Imaging, 2000, 18: 671-679"; - + Option ("unipolar", "optimise assuming a unipolar electrostatic repulsion model rather than the bipolar model normally assumed in DWI") + ARGUMENTS + + Argument ("ndir", "the number of directions to generate.").type_integer (6, std::numeric_limits::max()) + + Argument ("dirs", "the text file to write the directions to, as [ az el ] pairs.").type_file_out(); - + Option ("cartesian", "Output the directions in Cartesian coordinates [x y z] instead of [az el]."); + OPTIONS + + Option ("power", "specify exponent to use for repulsion power law (default: " + str(DEFAULT_POWER) + "). This must be a power of 2 (i.e. 1, 2, 4, 8, 16, ...).") + + Argument ("exp").type_integer (1, std::numeric_limits::max()) + + + Option ("niter", "specify the maximum number of iterations to perform (default: " + str(DEFAULT_NITER) + ").") + + Argument ("num").type_integer (1, std::numeric_limits::max()) + + + Option ("unipolar", "optimise assuming a unipolar electrostatic repulsion model rather than the bipolar model normally assumed in DWI") + + + Option ("cartesian", "Output the directions in Cartesian coordinates [x y z] instead of [az el]."); } @@ -116,7 +124,8 @@ class Energy { MEMALIGN(Energy) Eigen::Vector3d r = d1-d2; double _1_r2 = 1.0 / r.squaredNorm(); - double e = fast_pow (_1_r2, power/2); + double _1_r = std::sqrt (_1_r2); + double e = fast_pow (_1_r, power); E += e; g1 -= (power * e * _1_r2) * r; g2 += (power * e * _1_r2) * r; @@ -124,7 +133,8 @@ class Energy { MEMALIGN(Energy) if (bipolar) { r = d1+d2; _1_r2 = 1.0 / r.squaredNorm(); - e = fast_pow (_1_r2, power/2); + _1_r = std::sqrt (_1_r2); + e = fast_pow (_1_r, power); E += e; g1 -= (power * e * _1_r2) * r; g2 -= (power * e * _1_r2) * r; @@ -172,7 +182,7 @@ void run () { // optimisation proper: { ProgressBar progress ("Optimising directions"); - for (int power = 2; power <= target_power; power *= 2) { + for (int power = 1; power <= target_power; power *= 2) { Energy energy (ndirs, power, bipolar, directions); Math::GradientDescent optim (energy, ProjectedUpdate()); @@ -198,7 +208,7 @@ void run () { directions = optim.state(); progress.set_text ("Optimising directions (power " + str(power) - + ", energy: " + str(optim.value(), 8) + ", gradient: " + str(optim.gradient_norm(), 8) + ", iteration " + str(iter) + ")"); + + ", energy: " + str(optim.value(), 8) + ", gradient: " + str(optim.gradient_norm(), 8) + ", iteration " + str(iter) + ")"); } } From e5897db5f72ab9c7f71e7b56efb653748ab3a4ca Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Tue, 11 Jul 2017 16:15:52 +0100 Subject: [PATCH 069/139] dirgen: implement multiple restarts to improve robustness --- cmd/dirgen.cpp | 86 +++++++++++++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/cmd/dirgen.cpp b/cmd/dirgen.cpp index f3f0a87825..163a7817ab 100644 --- a/cmd/dirgen.cpp +++ b/cmd/dirgen.cpp @@ -21,6 +21,7 @@ #define DEFAULT_POWER 1 #define DEFAULT_NITER 10000 +#define DEFAULT_RESTARTS 10 using namespace MR; @@ -61,6 +62,9 @@ void usage () + Option ("niter", "specify the maximum number of iterations to perform (default: " + str(DEFAULT_NITER) + ").") + Argument ("num").type_integer (1, std::numeric_limits::max()) + + Option ("restarts", "specify the number of restarts to perform (default: " + str(DEFAULT_RESTARTS) + ").") + + Argument ("num").type_integer (1, std::numeric_limits::max()) + + Option ("unipolar", "optimise assuming a unipolar electrostatic repulsion model rather than the bipolar model normally assumed in DWI") + Option ("cartesian", "Output the directions in Cartesian coordinates [x y z] instead of [az el]."); @@ -162,59 +166,77 @@ class Energy { MEMALIGN(Energy) void run () { size_t niter = get_option_value ("niter", DEFAULT_NITER); + size_t restarts = get_option_value ("restarts", DEFAULT_RESTARTS); int target_power = get_option_value ("power", DEFAULT_POWER); bool bipolar = !(get_options ("unipolar").size()); int ndirs = to (argument[0]); Eigen::VectorXd directions (3*ndirs); + Eigen::VectorXd best_directions (3*ndirs); + double best_E = std::numeric_limits::infinity(); + size_t best_start = 0; + - { // random initialisation: - Math::RNG::Normal rng; - for (ssize_t n = 0; n < ndirs; ++n) { - auto d = directions.segment (3*n,3); - d[0] = rng(); - d[1] = rng(); - d[2] = rng(); - d.normalize(); + for (size_t start = 0; start < restarts; ++start) { + + { // random initialisation: + Math::RNG::Normal rng; + for (ssize_t n = 0; n < ndirs; ++n) { + auto d = directions.segment (3*n,3); + d[0] = rng(); + d[1] = rng(); + d[2] = rng(); + d.normalize(); + } } - } - // optimisation proper: - { - ProgressBar progress ("Optimising directions"); - for (int power = 1; power <= target_power; power *= 2) { - Energy energy (ndirs, power, bipolar, directions); + double E = 0.0; - Math::GradientDescent optim (energy, ProjectedUpdate()); + // optimisation proper: + { + ProgressBar progress ("Optimising directions [" + str(start) + "]"); + for (int power = 1; power <= target_power; power *= 2) { + Energy energy (ndirs, power, bipolar, directions); - INFO ("setting power = " + str (power)); - optim.init(); + Math::GradientDescent optim (energy, ProjectedUpdate()); - //Math::check_function_gradient (energy, optim.state(), 0.001); - //return; + INFO ("setting power = " + str (power)); + optim.init(); - size_t iter = 0; - for (; iter < niter; iter++) { - if (!optim.iterate()) - break; + //Math::check_function_gradient (energy, optim.state(), 0.001); + //return; - INFO ("[ " + str (iter) + " ] (pow = " + str (power) + ") E = " + str (optim.value(), 8) - + ", grad = " + str (optim.gradient_norm(), 8)); + size_t iter = 0; + for (; iter < niter; iter++) { + if (!optim.iterate()) + break; - progress.update ([&]() { return "Optimising directions (power " + str(power) - + ", energy: " + str(optim.value(), 8) + ", gradient: " + str(optim.gradient_norm(), 8) + ", iteration " + str(iter) + ")"; }); - } + INFO ("[ " + str (iter) + " ] (pow = " + str (power) + ") E = " + str (optim.value(), 8) + + ", grad = " + str (optim.gradient_norm(), 8)); + + progress.update ([&]() { return "Optimising directions [" + str(start) + "] (power " + str(power) + + ", energy: " + str(optim.value(), 8) + ", gradient: " + str(optim.gradient_norm(), 8) + ", iteration " + str(iter) + ")"; }); + } - directions = optim.state(); + directions = optim.state(); + E = optim.value(); + + progress.set_text ("Optimising directions [" + str(start) + "] (power " + str(power) + + ", energy: " + str(optim.value(), 8) + ", gradient: " + str(optim.gradient_norm(), 8) + ", iteration " + str(iter) + ")"); + } + } - progress.set_text ("Optimising directions (power " + str(power) - + ", energy: " + str(optim.value(), 8) + ", gradient: " + str(optim.gradient_norm(), 8) + ", iteration " + str(iter) + ")"); + if (E < best_E) { + best_start = start; + best_E = E; + best_directions = directions; } } + CONSOLE ("outputting start " + str(best_start) + " (E = " + str(best_E) + ")"); Eigen::MatrixXd directions_matrix (ndirs, 3); for (int n = 0; n < ndirs; ++n) - directions_matrix.row (n) = directions.segment (3*n, 3); + directions_matrix.row (n) = best_directions.segment (3*n, 3); DWI::Directions::save (directions_matrix, argument[1], get_options ("cartesian").size()); } From c8808eaecb4a09ec8b5da4f545015b1391c88e5a Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Wed, 12 Jul 2017 10:37:59 +1000 Subject: [PATCH 070/139] mtnormalise: Changing progress bar for normalisation computation to use a percentage target --- cmd/mtnormalise.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/mtnormalise.cpp b/cmd/mtnormalise.cpp index b99f8e41d9..8e21f5922b 100644 --- a/cmd/mtnormalise.cpp +++ b/cmd/mtnormalise.cpp @@ -372,7 +372,7 @@ void run_primitive () { }; input_progress.done (); - ProgressBar progress ("performing log-domain intensity normalisation"); + ProgressBar progress ("performing log-domain intensity normalisation", max_iter); // Perform an initial outlier rejection prior to the first iteration outlier_rejection (3.f); @@ -382,7 +382,6 @@ void run_primitive () { while (iter <= max_iter) { INFO ("Iteration: " + str(iter)); - progress++; // Iteratively compute tissue balance factors with outlier rejection size_t balance_iter = 1; @@ -391,7 +390,6 @@ void run_primitive () { while (!balance_converged && balance_iter <= max_inner_iter) { DEBUG ("Balance and outlier rejection iteration " + str(balance_iter) + " starts."); - progress++; if (n_tissue_types > 1) { @@ -476,6 +474,8 @@ void run_primitive () { // Generate normalisation field in the image domain for (auto i = Loop (0, 3) (norm_field_log, norm_field_image); i; ++i) norm_field_image.value () = std::exp(norm_field_log.value()); + + progress++; iter++; } From 79638dd07eb513c87f5e905ef172da0c99a3ca5d Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Wed, 12 Jul 2017 11:37:15 +1000 Subject: [PATCH 071/139] mtnormalise: some more final cleaning up --- cmd/mtnormalise.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/mtnormalise.cpp b/cmd/mtnormalise.cpp index 8e21f5922b..d36adf1520 100644 --- a/cmd/mtnormalise.cpp +++ b/cmd/mtnormalise.cpp @@ -25,7 +25,7 @@ using namespace App; #define DEFAULT_NORM_VALUE 0.28209479177 #define DEFAULT_MAIN_ITER_VALUE 15 -#define DEFAULT_INNER_MAXITER_VALUE 7 +#define DEFAULT_BALANCE_MAXITER_VALUE 7 #define DEFAULT_POLY_ORDER 3 const char* poly_order_choices[] = { "0", "1", "2", "3", nullptr }; @@ -306,7 +306,7 @@ void run_primitive () { const float normalisation_value = get_option_value ("value", DEFAULT_NORM_VALUE); const float log_norm_value = std::log (normalisation_value); const size_t max_iter = get_option_value ("niter", DEFAULT_MAIN_ITER_VALUE); - const size_t max_inner_iter = DEFAULT_INNER_MAXITER_VALUE; + const size_t max_balance_iter = DEFAULT_BALANCE_MAXITER_VALUE; // Initialise normalisation fields in both image and log domain Eigen::MatrixXd norm_field_weights; @@ -325,8 +325,8 @@ void run_primitive () { // Store lambda-function for performing outlier-rejection. // We perform a coarse outlier-rejection initially as well as - // a finer outlier-rejection within the inner loop when computing - // tissue balance factors + // a finer outlier-rejection within each iteration of the + // tissue (re)balancing loop auto outlier_rejection = [&](float outlier_range) { auto summed_log = ImageType::scratch (header_3D, "Log of summed tissue volumes"); @@ -387,7 +387,7 @@ void run_primitive () { size_t balance_iter = 1; bool balance_converged = false; - while (!balance_converged && balance_iter <= max_inner_iter) { + while (!balance_converged && balance_iter <= max_balance_iter) { DEBUG ("Balance and outlier rejection iteration " + str(balance_iter) + " starts."); From bb35a9608d3472b2c0352379fbea5eac32fe2dc8 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 12 Jul 2017 15:17:43 +1000 Subject: [PATCH 072/139] dwipreproc & PhaseEncoding fixes - When using the -rpe_all option in dwipreproc, paired volumes between the two halves of the acquisitino were not being correctly found. - When calling applytopup, it appears that in some cases it will combine reversed phase-encode volume pairs into a single volume each, even when requesting Jacobian modulation (it did not do this in my own testing); this change explicitly calls applytopup separately for each unique phase encoding setting, to ensure that no such combination takes place. - Many commands that convert DWI volumes into some other mathematical representation were retaining the phase encoding scheme in the output header, resulting in errors being thrown when the number of entries in the table did not match the number of volumes. - dwi2mask was additionally failing to stash the DW scheme, as the relevant function was being called upon a temporary Header instance rather than the output image. --- bin/dwipreproc | 87 ++++++++++++++++++++++++++---------- cmd/amp2sh.cpp | 4 +- cmd/dwi2adc.cpp | 4 +- cmd/dwi2fod.cpp | 2 + cmd/dwi2mask.cpp | 7 ++- cmd/dwi2noise.cpp | 2 + cmd/dwi2tensor.cpp | 2 + core/filter/dwi_brain_mask.h | 5 ++- core/phase_encoding.cpp | 9 ++++ core/phase_encoding.h | 8 ++++ 10 files changed, 101 insertions(+), 29 deletions(-) diff --git a/bin/dwipreproc b/bin/dwipreproc index 8802f4d5ae..095d903037 100755 --- a/bin/dwipreproc +++ b/bin/dwipreproc @@ -2,7 +2,7 @@ # 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 (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. +# This script is generally one of the first operations 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 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. @@ -24,7 +24,7 @@ if not os.path.isdir(lib_folder): sys.path.insert(0, lib_folder) -import math +import math, shutil from distutils.spawn import find_executable from mrtrix3 import app, file, fsl, image, path, phaseEncoding, run @@ -182,6 +182,18 @@ if not len(grad) == num_volumes: do_topup = (not PE_design == 'None') +def grads_match(one, two): + # Dot product between gradient directions + dot_product = one[0]*two[0] + one[1]*two[1] + one[2]*two[2] + if abs(dot_product) < 0.999: + return False + # b-value + if abs(one[3]-two[3]) > 10.0: + return False + return True + + + # Manually generate a phase-encoding table for the input DWI based on user input dwi_manual_pe_scheme = None topup_manual_pe_scheme = None @@ -220,26 +232,37 @@ 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 + # Update: The possible permutations of volume-matched acquisition is limited within the + # context of the -rpe_all option. In particular, the potential for having more + # than one b=0 volume within each half means that it is not possible to permit + # arbitrary ordering of those pairs, since b=0 volumes would then be matched + # despite having the same phase-encoding direction. Instead, explicitly enforce + # that volumes must be matched between the first and second halves of the DWI data. elif PE_design == 'All': - grad_matchings = [ num_volumes ] * num_volumes + if num_volumes%2: + app.error('If using -rpe_all option, input image must contain an even number of volumes') + grads_matched = [ num_volumes ] * num_volumes grad_pairs = [ ] - 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 - if grad[index1] == grad[index2]: - grad_matchings[index1] = index2; - grad_matchings[index2] = index1; + for index1 in range(int(num_volumes/2)): + if grads_matched[index1] == num_volumes: # As yet unpaired + for index2 in range(int(num_volumes/2), num_volumes): + if grads_matched[index2] == num_volumes: # Also as yet unpaired + if grads_match(grad[index1], grad[index2]): + grads_matched[index1] = index2; + grads_matched[index2] = index1; grad_pairs.append([index1, index2]) + break + else: + app.error('Unable to determine matching reversed phase-encode direction volume for DWI volume ' + index1) if not len(grad_pairs) == num_volumes/2: - app.error('Unable to determine matching DWI volume pairs for reversed phase-encode combination') + app.error('Unable to determine complete 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): line = list(manual_pe_dir) - if grad_matchings[index] < index: + if index >= int(num_volumes/2): line = [ (-i if i else 0.0) for i in line ] line.append(trt) dwi_manual_pe_scheme.append(line) @@ -435,22 +458,31 @@ if do_topup: else: run.command('mrinfo dwi.mif -export_pe_eddy applytopup_config.txt applytopup_indices.txt') - applytopup_index_list = [ ] + # Update: Call applytopup separately for each unique phase-encoding + # This should be the most compatible option with more complex phase-encoding acquisition designs, + # since we don't need to worry about applytopup performing volume recombination + # Plus, recombination doesn't need to be optimal; we're only using this to derive a brain mask applytopup_image_list = [ ] index = 1 with open('applytopup_config.txt', 'r') as f: for line in f: - image_path = 'dwi_pe_' + str(index) + '.nii' - run.command('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) + input_path = 'dwi_pe_' + str(index) + '.nii' + json_path = 'dwi_pe_' + str(index) + '.json' + temp_path = 'dwi_pe_' + str(index) + '_topup' + fsl_suffix + output_path = 'dwi_pe_' + str(index) + '_topup.mif' + run.command('dwiextract dwi.mif' + import_dwi_pe_table_option + ' -pe ' + ','.join(line.split()) + ' - | mrconvert - ' + input_path + ' -json_export ' + json_path) + run.command(applytopup_cmd + ' --imain=' + input_path + ' --datain=applytopup_config.txt --inindex=' + str(index) + ' --topup=field --out=' + temp_path + ' --method=jac') + file.delTempFile(input_path) + run.command('mrconvert ' + temp_path + ' ' + output_path + ' -json_import ' + json_path) + file.delTempFile(json_path) + file.delTempFile(temp_path) + applytopup_image_list.append(output_path) index += 1 - # Finally ready to run applytopup - run.command(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 - run.command('mrconvert dwi_post_topup' + fsl_suffix + ' -grad grad.b - | dwi2mask - - | maskfilter - dilate - | mrconvert - mask.nii -datatype float32 -stride -1,+2,+3') + run.command('mrcat ' + ' '.join(applytopup_image_list) + ' - | dwi2mask - - | maskfilter - dilate - | mrconvert - mask.nii -datatype float32 -stride -1,+2,+3') + for entry in applytopup_image_list: + file.delTempFile(entry) eddy_in_topup_option = ' --topup=field' @@ -496,14 +528,15 @@ 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 any(dwi_pe_scheme[index1][i] + dwi_pe_scheme[index2][i] for i in range(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 grads_match(grad[index1], grad[index2]): volume_matchings[index1] = index2; volume_matchings[index2] = index1; volume_pairs.append([index1, index2]) + break -if not len(volume_pairs) == num_volumes/2: +if not len(volume_pairs) == int(num_volumes/2): # Convert the resulting volume to the output image, and re-insert the diffusion encoding run.command('mrconvert dwi_post_eddy' + fsl_suffix + ' result.mif' + stride_option + ' -fslgrad ' + bvecs_path + ' bvals') @@ -533,6 +566,14 @@ else: 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] + # If one diffusion sensitisation gradient direction is reversed with respect to + # the other, still want to enable their recombination; but need to explicitly + # account for this when averaging the two directions + if norm2 < 0.999: + 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: factor = 1.0 / math.sqrt(norm2) diff --git a/cmd/amp2sh.cpp b/cmd/amp2sh.cpp index 982e6e7885..b34a1915cb 100644 --- a/cmd/amp2sh.cpp +++ b/cmd/amp2sh.cpp @@ -13,8 +13,9 @@ #include "command.h" -#include "progressbar.h" #include "image.h" +#include "phase_encoding.h" +#include "progressbar.h" #include "math/SH.h" #include "dwi/gradient.h" #include "dwi/shells.h" @@ -232,6 +233,7 @@ void run () DWI::stash_DW_scheme (header, grad); } } + PhaseEncoding::clear_scheme (header); auto sh2amp = DWI::compute_SH2amp_mapping (dirs, true, 8); diff --git a/cmd/dwi2adc.cpp b/cmd/dwi2adc.cpp index 345631d78f..d599486a0f 100644 --- a/cmd/dwi2adc.cpp +++ b/cmd/dwi2adc.cpp @@ -13,8 +13,9 @@ #include "command.h" -#include "progressbar.h" #include "image.h" +#include "phase_encoding.h" +#include "progressbar.h" #include "algo/threaded_copy.h" #include "math/least_squares.h" #include "dwi/gradient.h" @@ -99,6 +100,7 @@ void run () { header.ndim() = 4; header.size(3) = 2; DWI::stash_DW_scheme (header, grad); + PhaseEncoding::clear_scheme (header); auto adc = Image::create (argument[1], header); diff --git a/cmd/dwi2fod.cpp b/cmd/dwi2fod.cpp index 20d9ce1bbc..e6e1b3b4b2 100644 --- a/cmd/dwi2fod.cpp +++ b/cmd/dwi2fod.cpp @@ -15,6 +15,7 @@ #include "command.h" #include "header.h" #include "image.h" +#include "phase_encoding.h" #include "algo/threaded_loop.h" #include "dwi/gradient.h" #include "dwi/shells.h" @@ -249,6 +250,7 @@ void run () header_out.size(3) = shared.nSH(); DWI::stash_DW_scheme (header_out, shared.grad); + PhaseEncoding::clear_scheme (header_out); auto fod = Image::create (argument[3], header_out); CSD_Processor processor (shared, mask); diff --git a/cmd/dwi2mask.cpp b/cmd/dwi2mask.cpp index e36efd0b92..9b1e146d29 100644 --- a/cmd/dwi2mask.cpp +++ b/cmd/dwi2mask.cpp @@ -75,7 +75,10 @@ void run () { auto temp_mask = Image::scratch (dwi_brain_mask_filter, "brain mask"); dwi_brain_mask_filter (input, temp_mask); - auto output = Image::create (argument[1], temp_mask); + Header H_out (temp_mask); + DWI::stash_DW_scheme (H_out, grad); + PhaseEncoding::clear_scheme (H_out); + auto output = Image::create (argument[1], H_out); unsigned int scale = get_option_value ("clean_scale", DEFAULT_CLEAN_SCALE); @@ -84,7 +87,7 @@ void run () { Filter::MaskClean clean_filter (temp_mask, std::string("applying mask cleaning filter")); clean_filter.set_scale (scale); clean_filter (temp_mask, output); - } catch (Exception& e) { + } catch (...) { WARN("Unable to run mask cleaning filter (image is not truly 3D); skipping"); for (auto l = Loop (0,3) (temp_mask, output); l; ++l) output.value() = temp_mask.value(); diff --git a/cmd/dwi2noise.cpp b/cmd/dwi2noise.cpp index c744bde862..d96eb64df8 100644 --- a/cmd/dwi2noise.cpp +++ b/cmd/dwi2noise.cpp @@ -14,6 +14,7 @@ #include "command.h" #include "image.h" +#include "phase_encoding.h" #include "adapter/extract.h" #include "dwi/gradient.h" #include "math/least_squares.h" @@ -83,6 +84,7 @@ void run () header.ndim() = 3; header.datatype() = DataType::Float32; DWI::stash_DW_scheme (header, grad); + PhaseEncoding::clear_scheme (header); auto noise = Image::create (argument[1], header); DWI::estimate_noise (dwi, noise, mapping); diff --git a/cmd/dwi2tensor.cpp b/cmd/dwi2tensor.cpp index a7c240d402..24cbd47c87 100644 --- a/cmd/dwi2tensor.cpp +++ b/cmd/dwi2tensor.cpp @@ -13,6 +13,7 @@ #include "command.h" +#include "phase_encoding.h" #include "progressbar.h" #include "image.h" #include "algo/threaded_copy.h" @@ -180,6 +181,7 @@ void run () header.datatype() = DataType::Float32; header.ndim() = 4; DWI::stash_DW_scheme (header, grad); + PhaseEncoding::clear_scheme (header); Image* predict = nullptr; opt = get_options ("predicted_signal"); diff --git a/core/filter/dwi_brain_mask.h b/core/filter/dwi_brain_mask.h index 21a5a75ebd..8c7ecd392b 100644 --- a/core/filter/dwi_brain_mask.h +++ b/core/filter/dwi_brain_mask.h @@ -17,6 +17,8 @@ #include "memory.h" #include "image.h" +#include "phase_encoding.h" +#include "progressbar.h" #include "filter/base.h" #include "filter/connected_components.h" #include "filter/median.h" @@ -25,7 +27,7 @@ #include "algo/copy.h" #include "algo/loop.h" #include "dwi/gradient.h" -#include "progressbar.h" + namespace MR { @@ -70,7 +72,6 @@ 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/core/phase_encoding.cpp b/core/phase_encoding.cpp index 8a0dd7c5b8..93cc300f7b 100644 --- a/core/phase_encoding.cpp +++ b/core/phase_encoding.cpp @@ -81,6 +81,15 @@ namespace MR + void clear_scheme (Header& header) + { + header.keyval().erase ("pe_scheme"); + header.keyval().erase ("PhaseEncodingDirection"); + header.keyval().erase ("TotalReadoutTime"); + } + + + Eigen::MatrixXd parse_scheme (const Header& header) { Eigen::MatrixXd PE; diff --git a/core/phase_encoding.h b/core/phase_encoding.h index 3ed3b09a10..4b3010051e 100644 --- a/core/phase_encoding.h +++ b/core/phase_encoding.h @@ -127,6 +127,14 @@ namespace MR + //! clear the phase encoding matrix from a header + /*! this will delete any trace of phase encoding information + * from the Header::keyval() structure of \a header. + */ + void clear_scheme (Header& header); + + + //! 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() From 3338926a01c2c5d54f039c150b8ec27a4d9cf133 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 12 Jul 2017 15:19:44 +1000 Subject: [PATCH 073/139] Docs: Update paths to reflect FHS --- docs/quantitative_structural_connectivity/act.rst | 2 +- docs/troubleshooting/FAQ.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/quantitative_structural_connectivity/act.rst b/docs/quantitative_structural_connectivity/act.rst index f077aa9c8a..3b191ab2f9 100644 --- a/docs/quantitative_structural_connectivity/act.rst +++ b/docs/quantitative_structural_connectivity/act.rst @@ -69,5 +69,5 @@ Alternative tissue segmentation software Users who wish to experiment with using tissue segmentations from different software sources are encouraged to do so; if a particular approach is shown to be effective we can add an appropriate script to MRtrix. The ``5ttgen`` script has a second algorithm, ``freesurfer``, which demonstrates how the output of different software can be manipulated to provide the tissue segmentations in the appropriate format. It is however not recommended to actually use this alternative algorithm for patient studies; many midbrain structures are not segmented by FreeSurfer, so the tracking may not behave as desired. -Users who wish to try manipulating the tissue segmentations from some alternative software into the 5TT format may find it most convenient to make a copy of one of the existing algorithms within the ``scripts/src/_5ttgen//`` directory, and modify accordingly. The ``5ttgen`` script will automatically detect the presence of the new algorithm, and make it available at the command-line. +Users who wish to try manipulating the tissue segmentations from some alternative software into the 5TT format may find it most convenient to make a copy of one of the existing algorithms within the ``lib/mrtrix3/_5ttgen/`` directory, and modify accordingly. The ``5ttgen`` script will automatically detect the presence of the new algorithm, and make it available at the command-line. diff --git a/docs/troubleshooting/FAQ.rst b/docs/troubleshooting/FAQ.rst index 3f3163817b..fca499c1f2 100644 --- a/docs/troubleshooting/FAQ.rst +++ b/docs/troubleshooting/FAQ.rst @@ -175,10 +175,10 @@ location of those libraries; e.g.: .. code-block:: console - $ export PYTHONPATH=/home/user/mrtrix3/scripts:$PYTHONPATH + $ export PYTHONPATH=/home/user/mrtrix3/lib:$PYTHONPATH $ ./my_script [arguments] (options) -(Replace the path to the *MRtrix3* scripts directory with the location of your +(Replace the path to the *MRtrix3* "lib" directory with the location of your own installation) ``tck2connectome`` no longer has the ``-contrast X`` option...? From 10b3e53f18ce0f887aa2f4a3af1b1aacc42e6d83 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 12 Jul 2017 16:15:18 +1000 Subject: [PATCH 074/139] dwipreproc: Force deletion of re-used temp files Otherwise use of `-nocleanup` option will result in "File already exists" errors. --- bin/dwipreproc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/dwipreproc b/bin/dwipreproc index 095d903037..44e047ed84 100755 --- a/bin/dwipreproc +++ b/bin/dwipreproc @@ -645,8 +645,8 @@ else: # Volume recombination equation described in Skare and Bammer 2010 run.command('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') - file.delTempFile('volume0.mif') - file.delTempFile('volume1.mif') + os.remove('volume0.mif') + os.remove('volume1.mif') for index in range(0, len(eddy_config)): file.delTempFile('weight' + str(index+1) + '.mif') From 6bf5962b0fd0adb74a8ce4f047d5351faa23ef4b Mon Sep 17 00:00:00 2001 From: "Pannek, Kerstin (H&B, Herston - RBWH)" Date: Thu, 13 Jul 2017 08:08:46 +1000 Subject: [PATCH 075/139] fixed calculation of energy --- cmd/dirflip.cpp | 2 +- cmd/dirmerge.cpp | 5 +++-- cmd/dirorder.cpp | 10 ++-------- cmd/dirsplit.cpp | 2 +- cmd/dirstat.cpp | 4 ++-- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/cmd/dirflip.cpp b/cmd/dirflip.cpp index e636fb6019..a0508d57d0 100644 --- a/cmd/dirflip.cpp +++ b/cmd/dirflip.cpp @@ -82,7 +82,7 @@ class Shared { MEMALIGN(Shared) vector3_type b = { directions(j,0), directions(j,1), directions(j,2) }; if (signs[i] < 0) a = -a; if (signs[j] < 0) b = -b; - return 1.0 / (a-b).squaredNorm(); + return 1.0 / (a-b).norm(); } diff --git a/cmd/dirmerge.cpp b/cmd/dirmerge.cpp index 6e1c287381..a793b60d0e 100644 --- a/cmd/dirmerge.cpp +++ b/cmd/dirmerge.cpp @@ -113,11 +113,12 @@ void run () // use combination of mono- and bi-polar electrostatic repulsion models // to ensure adequate coverage of eddy-current space as well as // orientation space. Use a moderate bias, favouring the bipolar model. - return 1.2 / ( + + return 1.0 / sqrt( Math::pow2 (b[0] - a[0]) + Math::pow2 (b[1] - a[1]) + Math::pow2 (b[2] - a[2]) - ) + 1.0 / ( + ) + 1.0 / sqrt( Math::pow2 (b[0] + a[0]) + Math::pow2 (b[1] + a[1]) + Math::pow2 (b[2] + a[2]) diff --git a/cmd/dirorder.cpp b/cmd/dirorder.cpp index 03b520aee9..2112c46226 100644 --- a/cmd/dirorder.cpp +++ b/cmd/dirorder.cpp @@ -74,14 +74,8 @@ void run () ssize_t a = remaining[n]; for (size_t i = 0; i < indices.size(); ++i) { ssize_t b = indices[i]; - E += 1.0 / ( - Math::pow2 (directions(a,0)-directions(b,0)) + - Math::pow2 (directions(a,1)-directions(b,1)) + - Math::pow2 (directions(a,2)-directions(b,2))); - E += 1.0 / ( - Math::pow2 (directions(a,0)+directions(b,0)) + - Math::pow2 (directions(a,1)+directions(b,1)) + - Math::pow2 (directions(a,2)+directions(b,2))); + E += 1.0 / (directions.row(a) - directions.row(b)).norm(); + E += 1.0 / (directions.row(a) + directions.row(b)).norm(); } if (E < best_E) { best_E = E; diff --git a/cmd/dirsplit.cpp b/cmd/dirsplit.cpp index 1d2afef63b..64c86b90ee 100644 --- a/cmd/dirsplit.cpp +++ b/cmd/dirsplit.cpp @@ -85,7 +85,7 @@ class Shared { MEMALIGN(Shared) value_type energy (size_t i, size_t j) const { vector3_type a = { directions(i,0), directions(i,1), directions(i,2) }; vector3_type b = { directions(j,0), directions(j,1), directions(j,2) }; - return 1.0 / (a-b).squaredNorm() + 1.0 / (a+b).squaredNorm(); + return 1.0 / (a-b).norm() + 1.0 / (a+b).norm(); } diff --git a/cmd/dirstat.cpp b/cmd/dirstat.cpp index 3839f00b99..dcaf4ca91a 100644 --- a/cmd/dirstat.cpp +++ b/cmd/dirstat.cpp @@ -56,12 +56,12 @@ void report (const std::string& title, const Eigen::MatrixXd& directions) NN_bipolar[i] = std::max (NN_bipolar[i], cos_angle); NN_bipolar[j] = std::max (NN_bipolar[j], cos_angle); - double E = 1.0 / (directions.row(i).head(3) - directions.row(j).head(3)).squaredNorm(); + double E = 1.0 / (directions.row(i).head(3) - directions.row(j).head(3)).norm(); E_unipolar[i] += E; E_unipolar[j] += E; - E += 1.0 / (directions.row(i).head(3) + directions.row(j).head(3)).squaredNorm(); + E += 1.0 / (directions.row(i).head(3) + directions.row(j).head(3)).norm(); E_bipolar[i] += E; E_bipolar[j] += E; From af55ddbe51a117057967bca2c64cadba9f6fb4b7 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 13 Jul 2017 15:21:39 +1000 Subject: [PATCH 076/139] dwipreproc: Prevent variable name ghosting at final output Resulted in error "str() object has no method fromUser()" or the like, since variable name "path" was defined and therefore hides the module. --- bin/dwipreproc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/dwipreproc b/bin/dwipreproc index 44e047ed84..418f33874c 100755 --- a/bin/dwipreproc +++ b/bin/dwipreproc @@ -654,8 +654,8 @@ else: # Finally the recombined volumes must be concatenated to produce the resulting image series run.command('mrcat ' + ' '.join(combined_image_list) + ' - -axis 3 | mrconvert - result.mif -fslgrad bvecs_combined bvals_combined' + stride_option) - for path in combined_image_list: - file.delTempFile(path) + for entry in combined_image_list: + file.delTempFile(entry) From eb245c25bbc783954249da30c19bf2fd05274d6f Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 13 Jul 2017 17:02:17 +1000 Subject: [PATCH 077/139] dwipreproc: Speed up volume recombination --- bin/dwipreproc | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/bin/dwipreproc b/bin/dwipreproc index 418f33874c..2654886b4c 100755 --- a/bin/dwipreproc +++ b/bin/dwipreproc @@ -635,18 +635,28 @@ else: run.command('mrcalc ' + jacobian_path + ' ' + jacobian_path + ' -mult weight' + str(index+1) + '.mif') file.delTempFile(jacobian_path) + # If eddy provides its main image output in a compressed format, the code block below will need to + # uncompress that image independently for every volume pair. Instead, if this is the case, let's + # convert it to an uncompressed format before we do anything with it. + eddy_output = 'dwi_post_eddy' + fsl_suffix + if fsl_suffix.endswith('.gz'): + run.command('mrconvert ' + eddy_output + ' dwi_post_eddy.nii') + file.delTempFile(eddy_output) + eddy_output = 'dwi_post_eddy.nii' + # 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): pe_indices = [ eddy_indices[i] for i in volumes ] - run.command('mrconvert dwi_post_eddy' + fsl_suffix + ' volume0.mif -coord 3 ' + str(volumes[0])) - run.command('mrconvert dwi_post_eddy' + fsl_suffix + ' volume1.mif -coord 3 ' + str(volumes[1])) + run.command('mrconvert ' + eddy_output + ' volume0.mif -coord 3 ' + str(volumes[0])) + run.command('mrconvert ' + eddy_output + ' volume1.mif -coord 3 ' + str(volumes[1])) # Volume recombination equation described in Skare and Bammer 2010 run.command('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.remove('volume0.mif') os.remove('volume1.mif') + file.delTempFile(eddy_output) for index in range(0, len(eddy_config)): file.delTempFile('weight' + str(index+1) + '.mif') From a7698e5ff4efce58f3d77832b02725bcccffab90 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Thu, 13 Jul 2017 18:01:45 +1000 Subject: [PATCH 078/139] mtbin locked by default, override option for not recommended use --- cmd/mtbin.cpp | 12 ++++++++++-- docs/reference/commands/mtbin.rst | 4 +++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/cmd/mtbin.cpp b/cmd/mtbin.cpp index d2baed31c7..40d5bf6f3b 100644 --- a/cmd/mtbin.cpp +++ b/cmd/mtbin.cpp @@ -36,7 +36,7 @@ void usage () SYNOPSIS = "Multi-Tissue Bias field correction and Intensity Normalisation (WARNING: deprecated)."; DESCRIPTION - + "WARNING: this command is deprecated and may produce inappropriate results in several cases. Please use the new mtnormalise."; + + "WARNING: this command is deprecated and may produce highly inappropriate results in several cases. Not recommended and at your own discretion. Please use the new mtnormalise command instead for reliable results."; ARGUMENTS + Argument ("input output", "list of all input and output tissue compartment files. See example usage in the description. " @@ -60,7 +60,9 @@ void usage () + Argument ("number").type_integer() + Option ("check", "check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure. However, these regions are still corrected for bias fields based on the other image data.") - + Argument ("image").type_image_out (); + + Argument ("image").type_image_out () + + + Option ("override", "consciously use this deprecated command. Not recommended and at your own discretion."); } const int n_basis_vecs (20); @@ -126,6 +128,9 @@ void run () { WARN ("This command is deprecated and may produce inappropriate results in several cases. Please use the new mtnormalise."); + + if (!get_options("override").size()) + throw Exception ("This command is deprecated and not recommended for proper use of its original advertised functions. Check the option list for an option to consciously use this command anyway."); 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"); @@ -362,4 +367,7 @@ void run () output_image.value() = scale_factors(j, 0) * input_images[j].value() / bias_field.value(); } } + + WARN ("Using the output images as part of a pipeline to analyse one's data is discouraged."); + } diff --git a/docs/reference/commands/mtbin.rst b/docs/reference/commands/mtbin.rst index 05ebcf996d..15324d8da3 100644 --- a/docs/reference/commands/mtbin.rst +++ b/docs/reference/commands/mtbin.rst @@ -20,7 +20,7 @@ Usage Description ----------- -WARNING: this command is deprecated and may produce inappropriate results in several cases. Please use the new mtnormalise. +WARNING: this command is deprecated and may produce highly inappropriate results in several cases. Not recommended and at your own discretion. Please use the new mtnormalise command instead for reliable results. Options ------- @@ -37,6 +37,8 @@ Options - **-check image** check the final mask used to compute the bias field. This mask excludes outlier regions ignored by the bias field fitting procedure. However, these regions are still corrected for bias fields based on the other image data. +- **-override** consciously use this deprecated command. Not recommended and at your own discretion. + Standard options ^^^^^^^^^^^^^^^^ From 1c6877740a1b9fb1a35118f4cd33f2ca6948c8e5 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Thu, 13 Jul 2017 11:19:53 +0100 Subject: [PATCH 079/139] dirmerge: reinstate moderate bias towards unipolar model and add option to modify it (note this changes the default balance between unipolar and bipolar slightly from what it was). Also simplify the energy calculation using Eigen types. --- cmd/dirmerge.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/cmd/dirmerge.cpp b/cmd/dirmerge.cpp index a793b60d0e..e65379bc4b 100644 --- a/cmd/dirmerge.cpp +++ b/cmd/dirmerge.cpp @@ -38,12 +38,17 @@ ARGUMENTS + Argument ("out", "the output directions file, with each row listing " "the X Y Z gradient directions, the b-value, and an index representing " "the phase encode direction").type_file_out(); + +OPTIONS + + Option ("unipolar_weight", + "set the weight given to the unipolar electrostatic repulsion model compared to the " + "bipolar model (default: 0.2)."); } using value_type = double; -using Direction = std::array; +using Direction = Eigen::Matrix; using DirectionSet = vector; @@ -62,6 +67,8 @@ inline std::ostream& operator<< (std::ostream& stream, const OutDir& d) { void run () { size_t num_subsets = argument[0]; + value_type unipolar_weight = App::get_option_value ("unipolar_weight", 0.2); + value_type bipolar_weight = 1.0 - unipolar_weight; vector> dirs; @@ -80,7 +87,7 @@ void run () auto m = DWI::Directions::load_cartesian (argument[current++]); DirectionSet set; for (ssize_t r = 0; r < m.rows(); ++r) - set.push_back ({ { m(r,0), m(r,1), m(r,2) } }); + set.push_back (Direction (m(r,0), m(r,1), m(r,2))); d.push_back (set); } INFO ("found b = " + str(bvalue[nb]) + ", " + @@ -104,25 +111,18 @@ void run () auto push = [&](size_t b, size_t p, size_t n) { - merged.push_back ({ { { dirs[b][p][n][0], dirs[b][p][n][1], dirs[b][p][n][2] } }, b, p }); + merged.push_back ({ Direction (dirs[b][p][n][0], dirs[b][p][n][1], dirs[b][p][n][2]), b, p }); dirs[b][p].erase (dirs[b][p].begin()+n); }; - auto energy_pair = [](const Direction& a, const Direction& b) + auto energy_pair = [&](const Direction& a, const Direction& b) { // use combination of mono- and bi-polar electrostatic repulsion models // to ensure adequate coverage of eddy-current space as well as // orientation space. Use a moderate bias, favouring the bipolar model. - return 1.0 / sqrt( - Math::pow2 (b[0] - a[0]) + - Math::pow2 (b[1] - a[1]) + - Math::pow2 (b[2] - a[2]) - ) + 1.0 / sqrt( - Math::pow2 (b[0] + a[0]) + - Math::pow2 (b[1] + a[1]) + - Math::pow2 (b[2] + a[2]) - ); + return (unipolar_weight+bipolar_weight) / (b-a).norm() + + bipolar_weight / (b+a).norm(); }; auto energy = [&](size_t b, size_t p, size_t n) From 49eb6de820449e138ef7d416b05cfc966a7af54d Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Thu, 13 Jul 2017 11:58:01 +0100 Subject: [PATCH 080/139] dirstat: report identical energy as dirgen --- cmd/dirstat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/dirstat.cpp b/cmd/dirstat.cpp index dcaf4ca91a..960de4b7b4 100644 --- a/cmd/dirstat.cpp +++ b/cmd/dirstat.cpp @@ -98,7 +98,7 @@ void report (const std::string& title, const Eigen::MatrixXd& directions) min = std::min (min, e); max = std::max (max, e); } - print (" energy: total = " + str(total, precision) + ", mean = " + str(total/E.size(), precision) + ", range [ " + str(min, precision) + " - " + str(max, precision) + " ]\n"); + print (" energy: total = " + str(0.5*total, precision) + ", mean = " + str(total/E.size(), precision) + ", range [ " + str(min, precision) + " - " + str(max, precision) + " ]\n"); }; From 96be4472761e9e77339b9dd1c7266c4a597733de Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Thu, 13 Jul 2017 13:00:31 +0100 Subject: [PATCH 081/139] dirgen: multi-thread multiople restarts --- cmd/dirgen.cpp | 164 ++++++++++++++++++++++++++++--------------------- 1 file changed, 94 insertions(+), 70 deletions(-) diff --git a/cmd/dirgen.cpp b/cmd/dirgen.cpp index 163a7817ab..ba57603e8a 100644 --- a/cmd/dirgen.cpp +++ b/cmd/dirgen.cpp @@ -15,6 +15,7 @@ #include "command.h" #include "progressbar.h" #include "math/rng.h" +#include "thread.h" #include "math/gradient_descent.h" #include "math/check_gradient.h" #include "dwi/directions/file.h" @@ -91,13 +92,18 @@ class ProjectedUpdate { MEMALIGN(ProjectedUpdate) + + + + class Energy { MEMALIGN(Energy) public: - Energy (size_t n_directions, int power, bool bipolar, const Eigen::VectorXd& init_directions) : - ndir (n_directions), - power (power), - bipolar (bipolar), - init_directions (init_directions) { } + Energy (ProgressBar& progress) : + progress (progress), + ndirs (to (argument[0])), + bipolar (!(get_options ("unipolar").size())), + power (0), + directions (3 * ndirs) { } FORCE_INLINE double fast_pow (double x, int p) { return p == 1 ? x : fast_pow (x*x, p/2); @@ -105,24 +111,35 @@ class Energy { MEMALIGN(Energy) using value_type = double; - size_t size () const { return 3 * ndir; } + size_t size () const { return 3 * ndirs; } // set x to original directions provided in constructor. // The idea is to save the directions from one run to initialise next run // at higher power. - double init (Eigen::VectorXd& x) { - x = init_directions; + double init (Eigen::VectorXd& x) + { + Math::RNG::Normal rng; + for (ssize_t n = 0; n < ndirs; ++n) { + auto d = x.segment (3*n,3); + d[0] = rng(); + d[1] = rng(); + d[2] = rng(); + d.normalize(); + } return 0.01; } + + + // function executed by optimiser at each iteration: double operator() (const Eigen::VectorXd& x, Eigen::VectorXd& g) { double E = 0.0; g.setZero(); - for (size_t i = 0; i < ndir-1; ++i) { + for (size_t i = 0; i < ndirs-1; ++i) { auto d1 = x.segment (3*i, 3); auto g1 = g.segment (3*i, 3); - for (size_t j = i+1; j < ndir; ++j) { + for (size_t j = i+1; j < ndirs; ++j) { auto d2 = x.segment (3*j, 3); auto g2 = g.segment (3*j, 3); @@ -148,95 +165,102 @@ class Energy { MEMALIGN(Energy) } // constrain gradients to lie tangent to unit sphere: - for (size_t n = 0; n < ndir; ++n) + for (size_t n = 0; n < ndirs; ++n) g.segment(3*n,3) -= x.segment(3*n,3).dot (g.segment(3*n,3)) * x.segment(3*n,3); return E; } - protected: - size_t ndir; - int power; - bool bipolar; - const Eigen::VectorXd& init_directions; -}; + // function executed per thread: + void execute () + { + size_t this_start = 0; + while ((this_start = current_start.fetch_add (1)) < restarts) { + INFO ("launching start " + str (this_start)); + double E = 0.0; + for (power = 1; power <= target_power; power *= 2) { + Math::GradientDescent optim (*this, ProjectedUpdate()); -void run () { - size_t niter = get_option_value ("niter", DEFAULT_NITER); - size_t restarts = get_option_value ("restarts", DEFAULT_RESTARTS); - int target_power = get_option_value ("power", DEFAULT_POWER); - bool bipolar = !(get_options ("unipolar").size()); - int ndirs = to (argument[0]); + INFO ("start " + str(this_start) + ": setting power = " + str (power)); + optim.init(); - Eigen::VectorXd directions (3*ndirs); - Eigen::VectorXd best_directions (3*ndirs); - double best_E = std::numeric_limits::infinity(); - size_t best_start = 0; + size_t iter = 0; + for (; iter < niter; iter++) { + if (!optim.iterate()) + break; + DEBUG ("start " + str(this_start) + ": [ " + str (iter) + " ] (pow = " + str (power) + ") E = " + str (optim.value(), 8) + + ", grad = " + str (optim.gradient_norm(), 8)); - for (size_t start = 0; start < restarts; ++start) { + std::lock_guard lock (mutex); + ++progress; + } - { // random initialisation: - Math::RNG::Normal rng; - for (ssize_t n = 0; n < ndirs; ++n) { - auto d = directions.segment (3*n,3); - d[0] = rng(); - d[1] = rng(); - d[2] = rng(); - d.normalize(); + directions = optim.state(); + E = optim.value(); + } + + + + std::lock_guard lock (mutex); + if (E < best_E) { + best_E = E; + best_directions = directions; + } } } - double E = 0.0; - // optimisation proper: - { - ProgressBar progress ("Optimising directions [" + str(start) + "]"); - for (int power = 1; power <= target_power; power *= 2) { - Energy energy (ndirs, power, bipolar, directions); + static size_t restarts; + static int target_power; + static size_t niter; + static double best_E; + static Eigen::VectorXd best_directions; - Math::GradientDescent optim (energy, ProjectedUpdate()); - - INFO ("setting power = " + str (power)); - optim.init(); + protected: + ProgressBar& progress; + size_t ndirs; + bool bipolar; + int power; + Eigen::VectorXd directions; + double E; - //Math::check_function_gradient (energy, optim.state(), 0.001); - //return; + static std::mutex mutex; + static std::atomic current_start; +}; - size_t iter = 0; - for (; iter < niter; iter++) { - if (!optim.iterate()) - break; - INFO ("[ " + str (iter) + " ] (pow = " + str (power) + ") E = " + str (optim.value(), 8) - + ", grad = " + str (optim.gradient_norm(), 8)); +size_t Energy::restarts (DEFAULT_RESTARTS); +int Energy::target_power (DEFAULT_POWER); +size_t Energy::niter (DEFAULT_NITER); +std::mutex Energy::mutex; +std::atomic Energy::current_start (0); +double Energy::best_E = std::numeric_limits::infinity(); +Eigen::VectorXd Energy::best_directions; - progress.update ([&]() { return "Optimising directions [" + str(start) + "] (power " + str(power) - + ", energy: " + str(optim.value(), 8) + ", gradient: " + str(optim.gradient_norm(), 8) + ", iteration " + str(iter) + ")"; }); - } - directions = optim.state(); - E = optim.value(); - progress.set_text ("Optimising directions [" + str(start) + "] (power " + str(power) - + ", energy: " + str(optim.value(), 8) + ", gradient: " + str(optim.gradient_norm(), 8) + ", iteration " + str(iter) + ")"); - } - } - if (E < best_E) { - best_start = start; - best_E = E; - best_directions = directions; - } +void run () +{ + Energy::restarts = get_option_value ("restarts", DEFAULT_RESTARTS); + Energy::target_power = get_option_value ("power", DEFAULT_POWER); + Energy::niter = get_option_value ("niter", DEFAULT_NITER); + + { + ProgressBar progress ("Optimising directions up to power " + str(Energy::target_power) + " (" + str(Energy::restarts) + " restarts)"); + Energy energy_functor (progress); + auto threads = Thread::run (Thread::multi (energy_functor), "energy function"); } - CONSOLE ("outputting start " + str(best_start) + " (E = " + str(best_E) + ")"); + CONSOLE ("final energy = " + str(Energy::best_E) + ")"); + size_t ndirs = Energy::best_directions.size()/3; Eigen::MatrixXd directions_matrix (ndirs, 3); for (int n = 0; n < ndirs; ++n) - directions_matrix.row (n) = best_directions.segment (3*n, 3); + directions_matrix.row (n) = Energy::best_directions.segment (3*n, 3); DWI::Directions::save (directions_matrix, argument[1], get_options ("cartesian").size()); } From 6034fc2f1e031cb121e0bda2ab182598223884c1 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Thu, 13 Jul 2017 13:16:39 +0100 Subject: [PATCH 082/139] dirgen: fix minor typo in final console output --- cmd/dirgen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/dirgen.cpp b/cmd/dirgen.cpp index ba57603e8a..9c44220677 100644 --- a/cmd/dirgen.cpp +++ b/cmd/dirgen.cpp @@ -256,7 +256,7 @@ void run () auto threads = Thread::run (Thread::multi (energy_functor), "energy function"); } - CONSOLE ("final energy = " + str(Energy::best_E) + ")"); + CONSOLE ("final energy = " + str(Energy::best_E)); size_t ndirs = Energy::best_directions.size()/3; Eigen::MatrixXd directions_matrix (ndirs, 3); for (int n = 0; n < ndirs; ++n) From febc0a47c4194ed85f385a36223ac99b92003e62 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 14 Jul 2017 18:07:35 +1000 Subject: [PATCH 083/139] mrcalc and mrmath: Erase DW / PE schemes --- cmd/mrcalc.cpp | 19 +++++++++++++++++++ cmd/mrmath.cpp | 22 ++++++++++++++++++---- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/cmd/mrcalc.cpp b/cmd/mrcalc.cpp index b4caaf91a3..df1b3fcf2c 100644 --- a/cmd/mrcalc.cpp +++ b/cmd/mrcalc.cpp @@ -15,8 +15,10 @@ #include "command.h" #include "image.h" #include "memory.h" +#include "phase_encoding.h" #include "math/rng.h" #include "algo/threaded_copy.h" +#include "dwi/gradient.h" using namespace MR; @@ -591,6 +593,23 @@ void get_header (const StackEntry& entry, Header& header) header.spacing(n) = entry.image->spacing(n); } + const auto header_grad = DWI::parse_DW_scheme (header); + if (header_grad.rows()) { + const auto entry_grad = DWI::parse_DW_scheme (*entry.image); + if (entry_grad.rows()) { + if (!entry_grad.isApprox (header_grad)) + header.keyval().erase("dw_scheme"); + } + } + + const auto header_pe = PhaseEncoding::get_scheme (header); + if (header_pe.rows()) { + const auto entry_pe = PhaseEncoding::get_scheme (*entry.image); + if (entry_pe.rows()) { + if (!entry_pe.isApprox (header_pe)) + PhaseEncoding::clear_scheme (header); + } + } } diff --git a/cmd/mrmath.cpp b/cmd/mrmath.cpp index 2915b8f0f5..5d93c50835 100644 --- a/cmd/mrmath.cpp +++ b/cmd/mrmath.cpp @@ -13,15 +13,16 @@ #include "command.h" -#include "progressbar.h" -#include "memory.h" #include "image.h" +#include "memory.h" +#include "phase_encoding.h" +#include "progressbar.h" #include "algo/threaded_loop.h" #include "math/math.h" #include "math/median.h" +#include "dwi/gradient.h" #include -#include using namespace MR; @@ -346,9 +347,17 @@ void run () if (axis >= image_in.ndim()) throw Exception ("Cannot perform operation along axis " + str (axis) + "; image only has " + str(image_in.ndim()) + " axes"); - Header header_out (image_in); + if (axis == 3) { + try { + const auto DW_scheme = DWI::parse_DW_scheme (header_out); + DWI::stash_DW_scheme (header_out, DW_scheme); + } catch (...) { } + header_out.keyval().erase ("dw_scheme"); + PhaseEncoding::clear_scheme (header_out); + } + header_out.datatype() = DataType::from_command_line (DataType::Float32); header_out.size(axis) = 1; squeeze_dim (header_out); @@ -408,6 +417,11 @@ void run () } } + // Wipe any header information that can't be guaranteed to still be accurate + // after applying an operator across multiple images + header.keyval().erase ("dw_scheme"); + PhaseEncoding::clear_scheme (header); + // Instantiate a kernel depending on the operation requested std::unique_ptr kernel; switch (op) { From 916a1089f323df2b73fee78b1887397bbf551777 Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Fri, 14 Jul 2017 18:11:14 +1000 Subject: [PATCH 084/139] mrview: lightbox: Polishing up viewing of volumes When viewing volumes: * Original volume is rendered in the top-left pane * Changing the layout of lightbox keeps the selected slice fixed * Toggling between the lightbox and other modes retains the state of the original top-left volume --- src/gui/mrview/mode/base.cpp | 2 ++ src/gui/mrview/mode/base.h | 1 + src/gui/mrview/mode/lightbox.cpp | 45 +++++++++++++++++++++++--------- src/gui/mrview/mode/lightbox.h | 3 ++- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/gui/mrview/mode/base.cpp b/src/gui/mrview/mode/base.cpp index 2709e2d26a..7814160c4b 100644 --- a/src/gui/mrview/mode/base.cpp +++ b/src/gui/mrview/mode/base.cpp @@ -142,11 +142,13 @@ namespace MR done_painting: update_overlays = false; + finished_paintGL (); ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; } void Base::paint (Projection&) { } + void Base::finished_paintGL () {} void Base::mouse_press_event () { } void Base::mouse_release_event () { } diff --git a/src/gui/mrview/mode/base.h b/src/gui/mrview/mode/base.h index e40c3e1d6b..fcc13280ff 100644 --- a/src/gui/mrview/mode/base.h +++ b/src/gui/mrview/mode/base.h @@ -206,6 +206,7 @@ namespace MR } void reset_view (); + virtual void finished_paintGL (); bool visible; }; diff --git a/src/gui/mrview/mode/lightbox.cpp b/src/gui/mrview/mode/lightbox.cpp index 307315cec1..6f6c5cf7eb 100644 --- a/src/gui/mrview/mode/lightbox.cpp +++ b/src/gui/mrview/mode/lightbox.cpp @@ -39,6 +39,9 @@ namespace MR { Image* img = image(); + if (render_volumes()) + current_slice_index = 0; + if(!img || prev_image_name != img->header().name()) image_changed_event(); else { @@ -100,11 +103,14 @@ namespace MR n_cols * n_rows, proj_focusdelta(projection, 0.f)); - set_current_slice_index((n_rows * n_cols) / 2); - update_slices_focusdelta(); - update_volume_indices(); + size_t slice_idx = render_volumes() ? + std::min(current_slice_index, (n_rows * n_cols) - 1) : (n_rows * n_cols) / 2; + + set_current_slice_index(slice_idx); + update_slices_focusdelta(); + frame_VB.clear(); frame_VAO.clear(); } @@ -114,11 +120,7 @@ namespace MR int prev_index = current_slice_index; current_slice_index = slice_index; - if (render_volumes()) { - window().set_image_volume(3, volume_indices[current_slice_index]); - } - - else if (prev_index != (int)current_slice_index) { + if (!render_volumes() && prev_index != (int)current_slice_index) { const Projection& slice_proj = slices_proj_focusdelta[current_slice_index].first; float focus_delta = slices_proj_focusdelta[current_slice_index].second; @@ -145,11 +147,10 @@ namespace MR if (!is_4d) return; - int n_vols = image()->image.size(3); int initial_vol = image()->image.index(3); for(int i = 0, N = volume_indices.size(); i < N; ++i) - volume_indices[i] = std::min(std::max(initial_vol + (int)volume_increment * (i - (int)current_slice_index), 0), n_vols - 1); + volume_indices[i] = initial_vol + (int)volume_increment * i; } void LightBox::draw_plane_primitive (int axis, Displayable::Shader& shader_program, Projection& with_projection) @@ -161,6 +162,17 @@ namespace MR ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; } + void LightBox::finished_paintGL () + { + // When rendering volumes, the initial state is always considered to be + // the first volume (i.e. top-left slice) + // However, for the purposes of correctly rendering voxel information we need the + // image volume to correspond to the selected volume + // Hence, this is called once paintGL has finished to restore the initial state + if(render_volumes()) + image()->image.index(3) = volume_indices[0]; + } + void LightBox::paint(Projection&) { ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; @@ -182,6 +194,7 @@ namespace MR } bool rend_vols = render_volumes(); + bool render_plane = true; size_t slice_idx = 0; for(size_t row = 0; row < n_rows; ++row) { @@ -195,8 +208,13 @@ namespace MR // because move_in_out_displacement is reliant on MVP setup_projection (plane(), slice_proj); - if (rend_vols) - image()->image.index(3) = volume_indices[slice_idx]; + if (rend_vols) { + int n_vols = image()->image.size(3); + if (volume_indices[slice_idx] >= 0 && volume_indices[slice_idx] < n_vols - 1) + image()->image.index(3) = volume_indices[slice_idx]; + else + render_plane = false; + } else { float focus_delta = slices_proj_focusdelta[slice_idx].second; @@ -204,7 +222,8 @@ namespace MR set_focus(orig_focus + slice_focus); } - draw_plane_primitive(plane(), slice_shader, slice_proj); + if (render_plane) + draw_plane_primitive(plane(), slice_shader, slice_proj); if(slice_idx == current_slice_index) { // Drawing plane may alter the depth test state diff --git a/src/gui/mrview/mode/lightbox.h b/src/gui/mrview/mode/lightbox.h index 6e896efaf5..f6a65ee35c 100644 --- a/src/gui/mrview/mode/lightbox.h +++ b/src/gui/mrview/mode/lightbox.h @@ -69,6 +69,7 @@ namespace MR protected: void draw_plane_primitive(int axis, Displayable::Shader& shader_program, Projection& with_projection) override; + void finished_paintGL() override; private: static size_t slice_index(size_t row, size_t col) { @@ -92,7 +93,7 @@ namespace MR bool layout_is_dirty; size_t current_slice_index; - vector volume_indices; + vector volume_indices; vector slices_proj_focusdelta; GL::VertexBuffer frame_VB; From 04210ef7cfee380c961920a6ff9594dce85e1524 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Mon, 17 Jul 2017 11:24:59 +0100 Subject: [PATCH 085/139] dirgen: update testing data --- testing/data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/data b/testing/data index add3412305..b20e9f7657 160000 --- a/testing/data +++ b/testing/data @@ -1 +1 @@ -Subproject commit add34123053ec1ab5fb207f5225204c3eadf4e84 +Subproject commit b20e9f765717f9afe8ca72937cbfe40048f7c9e4 From 7643042b864ed36a218b6b55eb0cae810cdc2333 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Mon, 17 Jul 2017 17:33:33 +0100 Subject: [PATCH 086/139] DICOM: add support for multiple image types in one series This is particularly problematic for multi-frame DICOMs that can contain a mixture of different types of images, with no requirement that they match in terms of image dimensions, data scaling, etc. This commit splits series by the ImageType string, so that the user can select the relevant one. --- core/file/dicom/image.cpp | 4 ++++ core/file/dicom/image.h | 20 +++++++++++--------- core/file/dicom/mapper.cpp | 5 +++-- core/file/dicom/quick_scan.cpp | 28 ++++++++++++++++++---------- core/file/dicom/quick_scan.h | 2 ++ core/file/dicom/select_cmdline.cpp | 4 ++-- core/file/dicom/series.cpp | 5 +++-- core/file/dicom/series.h | 6 +++--- core/file/dicom/study.cpp | 6 ++++-- core/file/dicom/study.h | 2 +- core/file/dicom/tree.cpp | 19 +++++++++++-------- 11 files changed, 62 insertions(+), 39 deletions(-) diff --git a/core/file/dicom/image.cpp b/core/file/dicom/image.cpp index e3b4929fdf..ef52fe6ed3 100644 --- a/core/file/dicom/image.cpp +++ b/core/file/dicom/image.cpp @@ -43,6 +43,10 @@ namespace MR { // process image-specific or per-frame items here: if (is_toplevel) { switch (item.group) { + case 0x0008U: + if (item.element == 0x0008U) + image_type = join (item.get_string(), " "); + return; case 0x0018U: switch (item.element) { case 0x0050U: diff --git a/core/file/dicom/image.h b/core/file/dicom/image.h index ed0eacf017..3508048298 100644 --- a/core/file/dicom/image.h +++ b/core/file/dicom/image.h @@ -31,21 +31,21 @@ namespace MR { public: Frame () { acq_dim[0] = acq_dim[1] = dim[0] = dim[1] = instance = series_num = acq = sequence = UINT_MAX; - position_vector[0] = position_vector[1] = position_vector[2] = NAN; - orientation_x[0] = orientation_x[1] = orientation_x[2] = NAN; - orientation_y[0] = orientation_y[1] = orientation_y[2] = NAN; - orientation_z[0] = orientation_z[1] = orientation_z[2] = NAN; - distance = NAN; - pixel_size[0] = pixel_size[1] = slice_thickness = slice_spacing = NAN; + position_vector[0] = position_vector[1] = position_vector[2] = NaN; + orientation_x[0] = orientation_x[1] = orientation_x[2] = NaN; + orientation_y[0] = orientation_y[1] = orientation_y[2] = NaN; + orientation_z[0] = orientation_z[1] = orientation_z[2] = NaN; + distance = NaN; + pixel_size[0] = pixel_size[1] = slice_thickness = slice_spacing = NaN; scale_intercept = 0.0; scale_slope = 1.0; - bvalue = G[0] = G[1] = G[2] = NAN; + bvalue = G[0] = G[1] = G[2] = NaN; data = bits_alloc = data_size = frame_offset = 0; DW_scheme_wrt_image = false; transfer_syntax_supported = true; pe_axis = 3; pe_sign = 0; - pixel_bandwidth = bandwidth_per_pixel_phase_encode = echo_time = NAN; + pixel_bandwidth = bandwidth_per_pixel_phase_encode = echo_time = NaN; echo_train_length = 0; } @@ -53,7 +53,7 @@ namespace MR { Eigen::Vector3 position_vector, orientation_x, orientation_y, orientation_z, G; default_type distance, pixel_size[2], slice_thickness, slice_spacing, scale_slope, scale_intercept, bvalue; size_t data, bits_alloc, data_size, frame_offset; - std::string filename; + std::string filename, image_type; bool DW_scheme_wrt_image, transfer_syntax_supported; size_t pe_axis; int pe_sign; @@ -64,6 +64,8 @@ namespace MR { bool operator< (const Frame& frame) const { if (series_num != frame.series_num) return series_num < frame.series_num; + if (image_type != frame.image_type) + return image_type < frame.image_type; if (acq != frame.acq) return acq < frame.acq; assert (std::isfinite (distance)); diff --git a/core/file/dicom/mapper.cpp b/core/file/dicom/mapper.cpp index 1e2d78a132..8a721af109 100644 --- a/core/file/dicom/mapper.cpp +++ b/core/file/dicom/mapper.cpp @@ -71,7 +71,8 @@ namespace MR { if (image_it->frames.size()) { std::sort (image_it->frames.begin(), image_it->frames.end(), compare_ptr_contents()); for (auto frame_it : image_it->frames) - frames.push_back (frame_it.get()); + if (frame_it->image_type == series_it->image_type) + frames.push_back (frame_it.get()); } // otherwise add image frame: else @@ -99,7 +100,7 @@ namespace MR { default_type slice_separation = Frame::get_slice_separation (frames, dim[1]); if (series[0]->study->name.size()) - add_line (H.keyval()["comments"], std::string ("study: " + series[0]->study->name)); + add_line (H.keyval()["comments"], std::string ("study: " + series[0]->study->name + " [ " + series[0]->image_type + " ]")); if (patient->DOB.size()) add_line (H.keyval()["comments"], std::string ("DOB: " + format_date (patient->DOB))); diff --git a/core/file/dicom/quick_scan.cpp b/core/file/dicom/quick_scan.cpp index 310c62a89b..e69a02e514 100644 --- a/core/file/dicom/quick_scan.cpp +++ b/core/file/dicom/quick_scan.cpp @@ -16,6 +16,7 @@ #include "file/dicom/definitions.h" #include "file/dicom/element.h" #include "file/dicom/csa_entry.h" +#include "debug.h" namespace MR { namespace File { @@ -33,6 +34,7 @@ namespace MR { study_ID.clear(); study_time.clear(); series.clear(); + image_type.clear(); series_date.clear(); series_time.clear(); sequence.clear(); @@ -42,9 +44,12 @@ namespace MR { Element item; try { item.set (filename, force_read); + std::string current_image_type; + bool in_frames = false; while (item.read()) { - if (item.is (0x0008U, 0x0020U)) study_date = item.get_string()[0]; + if (item.is (0x0008U, 0x0008U)) current_image_type = join (item.get_string(), " "); + else if (item.is (0x0008U, 0x0020U)) study_date = item.get_string()[0]; else if (item.is (0x0008U, 0x0021U)) series_date = item.get_string()[0]; else if (item.is (0x0008U, 0x0030U)) study_time = item.get_string()[0]; else if (item.is (0x0008U, 0x0031U)) series_time = item.get_string()[0]; @@ -61,13 +66,12 @@ namespace MR { else if (item.is (0x0028U, 0x0011U)) dim[0] = item.get_uint()[0]; else if (item.is (0x0028U, 0x0100U)) bits_alloc = item.get_uint()[0]; else if (item.is (0x7FE0U, 0x0010U)) data = item.offset (item.data); - else if (item.is (0x0008U, 0x0008U)) { - // exclude Siemens MPR info image: - // TODO: could handle this by splitting on basis on this entry - vector V (item.get_string()); - for (size_t n = 0; n < V.size(); n++) { - if (uppercase (V[n]) == "CSAPARALLEL") - return true; + else if (item.is (0xFFFEU, 0xE000U)) { + if (item.parents.size() && + item.parents.back().group == 0x5200U && + item.parents.back().element == 0x9230U) { // multi-frame item + if (in_frames) ++image_type[current_image_type]; + else in_frames = true; } } @@ -83,6 +87,8 @@ namespace MR { } } + ++image_type[current_image_type]; + transfer_syntax_supported = item.transfer_syntax_supported; } @@ -114,8 +120,10 @@ namespace MR { << file.series_number << "] " << ( file.series.size() ? file.series : "[unspecified]" ) << " - " << format_date (file.series_date) << " " - << format_time (file.series_time) << "\n sequence: " - << ( file.sequence.size() ? file.sequence : "[unspecified]" ) << "\n"; + << format_time (file.series_time) << "\n"; + for (const auto& type : file.image_type) + stream << " image type: " << type.first << " [ " << type.second << " frames ]\n"; + stream << " sequence: " << ( file.sequence.size() ? file.sequence : "[unspecified]" ) << "\n"; return stream; } diff --git a/core/file/dicom/quick_scan.h b/core/file/dicom/quick_scan.h index afbed9df92..1ee8eb9e29 100644 --- a/core/file/dicom/quick_scan.h +++ b/core/file/dicom/quick_scan.h @@ -15,6 +15,7 @@ #ifndef __file_dicom_quick_scan_h__ #define __file_dicom_quick_scan_h__ +#include #include "mrtrix.h" namespace MR { @@ -30,6 +31,7 @@ namespace MR { std::string patient, patient_ID, patient_DOB; std::string study, study_ID, study_date, study_time; std::string series, series_date, series_time, sequence; + std::map image_type; size_t series_number, bits_alloc, dim[2], data; bool transfer_syntax_supported; }; diff --git a/core/file/dicom/select_cmdline.cpp b/core/file/dicom/select_cmdline.cpp index 0c5b737c32..01a4f536e5 100644 --- a/core/file/dicom/select_cmdline.cpp +++ b/core/file/dicom/select_cmdline.cpp @@ -115,14 +115,14 @@ namespace MR { while (series.size() == 0) { fprintf (stderr, "Select series ('q' to abort):\n"); for (size_t i = 0; i < study.size(); i++) { - fprintf (stderr, " %2zu - %4zu %s images %8s %s (%s) [%zu]\n", + fprintf (stderr, " %2zu - %4zu %s images %8s %s (%s) [%zu] %s\n", i, study[i]->size(), ( study[i]->modality.size() ? study[i]->modality.c_str() : "" ), format_time (study[i]->time).c_str(), ( study[i]->name.size() ? study[i]->name.c_str() : "unnamed" ), ( (*study[i])[0]->sequence_name.size() ? (*study[i])[0]->sequence_name.c_str() : "?" ), - study[i]->number); + study[i]->number, study[i]->image_type.c_str()); } std::cerr << "? "; std::cin >> buf; diff --git a/core/file/dicom/series.cpp b/core/file/dicom/series.cpp index aeed76a878..dc860d7ce8 100644 --- a/core/file/dicom/series.cpp +++ b/core/file/dicom/series.cpp @@ -80,13 +80,14 @@ namespace MR { std::ostream& operator<< (std::ostream& stream, const Series& item) { - stream << MR::printf (" %4u - %4u %4s images %10s %8s %s\n", + stream << MR::printf (" %4u - %4u %4s images %10s %8s %s [ %s ]\n", item.number, item.size(), ( item.modality.size() ? item.modality.c_str() : "(?)" ), format_date(item.date).c_str(), format_time(item.time).c_str(), - item.name.c_str() ); + item.name.c_str(), + item.image_type.c_str() ); for (size_t n = 0; n < item.size(); n++) stream << *item[n]; diff --git a/core/file/dicom/series.h b/core/file/dicom/series.h index 90914b45a1..c5dca4bcdc 100644 --- a/core/file/dicom/series.h +++ b/core/file/dicom/series.h @@ -28,15 +28,15 @@ namespace MR { class Series : public vector> { NOMEMALIGN public: - Series (Study* parent, const std::string& series_name, size_t series_number, + Series (Study* parent, const std::string& series_name, size_t series_number, const std::string& image_type, const std::string& series_modality = "", const std::string& series_date = "", const std::string& series_time = "") : - study (parent), name (series_name), modality (series_modality), + study (parent), name (series_name), image_type (image_type), modality (series_modality), date (series_date), time (series_time) { number = series_number; } Study* study; - std::string name; + std::string name, image_type; size_t number; std::string modality; std::string date; diff --git a/core/file/dicom/study.cpp b/core/file/dicom/study.cpp index 8b674e0aac..c236026a0f 100644 --- a/core/file/dicom/study.cpp +++ b/core/file/dicom/study.cpp @@ -24,13 +24,15 @@ namespace MR { bool series_time_mismatch_warning_issued = false; } - std::shared_ptr Study::find (const std::string& series_name, size_t series_number, + std::shared_ptr Study::find (const std::string& series_name, size_t series_number, const std::string& image_type, const std::string& series_modality, const std::string& series_date, const std::string& series_time) { for (size_t n = 0; n < size(); n++) { bool match = true; if (series_name == (*this)[n]->name) { if (series_number == (*this)[n]->number) { + if (image_type != (*this)[n]->image_type) + match = false; if (series_modality.size() && (*this)[n]->modality.size()) if (series_modality != (*this)[n]->modality) match = false; @@ -59,7 +61,7 @@ namespace MR { } } - push_back (std::shared_ptr (new Series (this, series_name, series_number, series_modality, series_date, series_time))); + push_back (std::shared_ptr (new Series (this, series_name, series_number, image_type, series_modality, series_date, series_time))); return back(); } diff --git a/core/file/dicom/study.h b/core/file/dicom/study.h index 96de363608..f91241c90d 100644 --- a/core/file/dicom/study.h +++ b/core/file/dicom/study.h @@ -35,7 +35,7 @@ namespace MR { Patient* patient; std::string name, ID, date, time; - std::shared_ptr find (const std::string& series_name, size_t series_number, + std::shared_ptr find (const std::string& series_name, size_t series_number, const std::string& image_type, const std::string& series_modality = "", const std::string& series_date = "", const std::string& series_time = ""); }; diff --git a/core/file/dicom/tree.cpp b/core/file/dicom/tree.cpp index 5c44e43802..4f7f08a2a0 100644 --- a/core/file/dicom/tree.cpp +++ b/core/file/dicom/tree.cpp @@ -96,14 +96,17 @@ namespace MR { std::shared_ptr patient = find (reader.patient, reader.patient_ID, reader.patient_DOB); std::shared_ptr study = patient->find (reader.study, reader.study_ID, reader.study_date, reader.study_time); - std::shared_ptr series = study->find (reader.series, reader.series_number, reader.modality, reader.series_date, reader.series_time); - - std::shared_ptr image (new Image); - image->filename = filename; - image->series = series.get(); - image->sequence_name = reader.sequence; - image->transfer_syntax_supported = reader.transfer_syntax_supported; - series->push_back (image); + for (const auto& image_type : reader.image_type) { + std::shared_ptr series = study->find (reader.series, reader.series_number, image_type.first, reader.modality, reader.series_date, reader.series_time); + + std::shared_ptr image (new Image); + image->filename = filename; + image->series = series.get(); + image->sequence_name = reader.sequence; + image->image_type = image_type.first; + image->transfer_syntax_supported = reader.transfer_syntax_supported; + series->push_back (image); + } } From 86d3a12d2bfb48166c3ea4f74a11b53720dc9b83 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Mon, 17 Jul 2017 18:31:45 +0100 Subject: [PATCH 087/139] DICOM: show image type in GUI selection dialog --- src/gui/dialog/dicom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/dialog/dicom.cpp b/src/gui/dialog/dicom.cpp index 2d908f756f..a8c794afce 100644 --- a/src/gui/dialog/dicom.cpp +++ b/src/gui/dialog/dicom.cpp @@ -45,7 +45,7 @@ namespace MR itemData = (str (p->size()) + " " + (p->modality.size() ? p->modality : std::string()) + " images " + format_time (p->time) + " " + (p->name.size() ? p->name : std::string ("unnamed")) + " (" + ( (*p) [0]->sequence_name.size() ? (*p) [0]->sequence_name : std::string ("?")) + - ") [" + str (p->number) + "]").c_str(); + ") [" + str (p->number) + "] " + p->image_type).c_str(); } ~Item() { qDeleteAll (childItems); From 039c00b6f5dbcd9ef6352a5b5a2ec7978f774d4c Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 18 Jul 2017 11:15:22 +1000 Subject: [PATCH 088/139] dirgen and dirmerge docs update --- docs/reference/commands/dirgen.rst | 9 ++++++++- docs/reference/commands/dirmerge.rst | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/reference/commands/dirgen.rst b/docs/reference/commands/dirgen.rst index d7fe6eb6e6..166a7b9d2f 100644 --- a/docs/reference/commands/dirgen.rst +++ b/docs/reference/commands/dirgen.rst @@ -18,13 +18,20 @@ Usage - *ndir*: the number of directions to generate. - *dirs*: the text file to write the directions to, as [ az el ] pairs. +Description +----------- + +Directions are distributed by analogy to an electrostatic repulsion system, with each direction corresponding to a single electrostatic charge (for -unipolar), or a pair of diametrically opposed charges for the default bipolar case). The energy of the system is determined based on the Coulomb repulstion, which assumed the form 1/r^power, where r is the distance between any pair of charges, and p is the power assumed for the repulsion law (default: 1). The minimum energy state is obtained by gradient descent. + Options ------- -- **-power exp** specify exponent to use for repulsion power law (default: 2). This must be a power of 2 (i.e. 2, 4, 8, 16, ...). +- **-power exp** specify exponent to use for repulsion power law (default: 1). This must be a power of 2 (i.e. 1, 2, 4, 8, 16, ...). - **-niter num** specify the maximum number of iterations to perform (default: 10000). +- **-restarts num** specify the number of restarts to perform (default: 10). + - **-unipolar** optimise assuming a unipolar electrostatic repulsion model rather than the bipolar model normally assumed in DWI - **-cartesian** Output the directions in Cartesian coordinates [x y z] instead of [az el]. diff --git a/docs/reference/commands/dirmerge.rst b/docs/reference/commands/dirmerge.rst index 67e377793e..ea00e52b90 100644 --- a/docs/reference/commands/dirmerge.rst +++ b/docs/reference/commands/dirmerge.rst @@ -22,6 +22,8 @@ Usage Options ------- +- **-unipolar_weight** set the weight given to the unipolar electrostatic repulsion model compared to the bipolar model (default: 0.2). + Standard options ^^^^^^^^^^^^^^^^ From f6a205cc3e78afa044925596d0ddbccef057ccaa Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 18 Jul 2017 11:17:52 +1000 Subject: [PATCH 089/139] dirgen: fixed a couple of typos in the description --- cmd/dirgen.cpp | 4 ++-- docs/reference/commands/dirgen.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/dirgen.cpp b/cmd/dirgen.cpp index 9c44220677..b481ef0479 100644 --- a/cmd/dirgen.cpp +++ b/cmd/dirgen.cpp @@ -38,8 +38,8 @@ void usage () DESCRIPTION + "Directions are distributed by analogy to an electrostatic repulsion system, with each direction " "corresponding to a single electrostatic charge (for -unipolar), or a pair of diametrically opposed charges " - "for the default bipolar case). The energy of the system is determined based on the Coulomb repulstion, " - "which assumed the form 1/r^power, where r is the distance between any pair of charges, and p is the power " + "(for the default bipolar case). The energy of the system is determined based on the Coulomb repulsion, " + "which assumes the form 1/r^power, where r is the distance between any pair of charges, and p is the power " "assumed for the repulsion law (default: 1). The minimum energy state is obtained by gradient descent."; diff --git a/docs/reference/commands/dirgen.rst b/docs/reference/commands/dirgen.rst index 166a7b9d2f..ee52425850 100644 --- a/docs/reference/commands/dirgen.rst +++ b/docs/reference/commands/dirgen.rst @@ -21,7 +21,7 @@ Usage Description ----------- -Directions are distributed by analogy to an electrostatic repulsion system, with each direction corresponding to a single electrostatic charge (for -unipolar), or a pair of diametrically opposed charges for the default bipolar case). The energy of the system is determined based on the Coulomb repulstion, which assumed the form 1/r^power, where r is the distance between any pair of charges, and p is the power assumed for the repulsion law (default: 1). The minimum energy state is obtained by gradient descent. +Directions are distributed by analogy to an electrostatic repulsion system, with each direction corresponding to a single electrostatic charge (for -unipolar), or a pair of diametrically opposed charges (for the default bipolar case). The energy of the system is determined based on the Coulomb repulsion, which assumes the form 1/r^power, where r is the distance between any pair of charges, and p is the power assumed for the repulsion law (default: 1). The minimum energy state is obtained by gradient descent. Options ------- From 09d278e0dea4556d20d65067af8a50639b514da2 Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Tue, 18 Jul 2017 13:31:09 +1000 Subject: [PATCH 090/139] mrview: lightbox: make sure volume increment value persists even after toggling between view modes --- src/gui/mrview/mode/lightbox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/mrview/mode/lightbox.cpp b/src/gui/mrview/mode/lightbox.cpp index 6f6c5cf7eb..bc2323492b 100644 --- a/src/gui/mrview/mode/lightbox.cpp +++ b/src/gui/mrview/mode/lightbox.cpp @@ -45,7 +45,7 @@ namespace MR if(!img || prev_image_name != img->header().name()) image_changed_event(); else { - set_volume_increment(1); + set_volume_increment(volume_increment); set_slice_increment(slice_focus_increment); } } From 0533ed980118f1da54c46f93b57991547844614f Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Tue, 18 Jul 2017 16:58:31 +1000 Subject: [PATCH 091/139] PE and DW scheme: Check before deleting map entries Inputs to std::map::erase() must be dereferenceable, which means that attempting to erase based on a string key that doesn't exist will result in an error. This had been used in a number of places, but testing had simply not yet raised the issue. --- cmd/mrcalc.cpp | 2 +- cmd/mrmath.cpp | 2 +- core/phase_encoding.cpp | 7 ++++--- core/phase_encoding.h | 15 ++++++++------- src/dwi/gradient.cpp | 9 +++++++++ src/dwi/gradient.h | 14 +++++++++++--- 6 files changed, 34 insertions(+), 15 deletions(-) diff --git a/cmd/mrcalc.cpp b/cmd/mrcalc.cpp index df1b3fcf2c..d2849fa2b1 100644 --- a/cmd/mrcalc.cpp +++ b/cmd/mrcalc.cpp @@ -598,7 +598,7 @@ void get_header (const StackEntry& entry, Header& header) const auto entry_grad = DWI::parse_DW_scheme (*entry.image); if (entry_grad.rows()) { if (!entry_grad.isApprox (header_grad)) - header.keyval().erase("dw_scheme"); + DWI::clear_DW_scheme (header); } } diff --git a/cmd/mrmath.cpp b/cmd/mrmath.cpp index 5d93c50835..e88fec70c2 100644 --- a/cmd/mrmath.cpp +++ b/cmd/mrmath.cpp @@ -354,7 +354,7 @@ void run () const auto DW_scheme = DWI::parse_DW_scheme (header_out); DWI::stash_DW_scheme (header_out, DW_scheme); } catch (...) { } - header_out.keyval().erase ("dw_scheme"); + DWI::clear_DW_scheme (header_out); PhaseEncoding::clear_scheme (header_out); } diff --git a/core/phase_encoding.cpp b/core/phase_encoding.cpp index 93cc300f7b..baa03159c0 100644 --- a/core/phase_encoding.cpp +++ b/core/phase_encoding.cpp @@ -83,9 +83,10 @@ namespace MR void clear_scheme (Header& header) { - header.keyval().erase ("pe_scheme"); - header.keyval().erase ("PhaseEncodingDirection"); - header.keyval().erase ("TotalReadoutTime"); + auto erase = [&] (const std::string& s) { auto it = header.keyval().find (s); if (it != header.keyval().end()) header.keyval().erase (it); }; + erase ("pe_scheme"); + erase ("PhaseEncodingDirection"); + erase ("TotalReadoutTime"); } diff --git a/core/phase_encoding.h b/core/phase_encoding.h index 4b3010051e..0c27b93389 100644 --- a/core/phase_encoding.h +++ b/core/phase_encoding.h @@ -88,10 +88,11 @@ namespace MR template void set_scheme (Header& header, const MatrixType& PE) { + auto erase = [&] (const std::string& s) { auto it = header.keyval().find (s); if (it != header.keyval().end()) header.keyval().erase (it); }; if (!PE.rows()) { - header.keyval().erase ("pe_scheme"); - header.keyval().erase ("PhaseEncodingDirection"); - header.keyval().erase ("TotalReadoutTime"); + erase ("pe_scheme"); + erase ("PhaseEncodingDirection"); + erase ("TotalReadoutTime"); return; } PhaseEncoding::check (header, PE); @@ -112,16 +113,16 @@ namespace MR } if (variation) { header.keyval()["pe_scheme"] = pe_scheme; - header.keyval().erase ("PhaseEncodingDirection"); - header.keyval().erase ("TotalReadoutTime"); + erase ("PhaseEncodingDirection"); + erase ("TotalReadoutTime"); } else { - header.keyval().erase ("pe_scheme"); + 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"] = str(PE(0, 3), 3); else - header.keyval().erase ("TotalReadoutTime"); + erase ("TotalReadoutTime"); } } diff --git a/src/dwi/gradient.cpp b/src/dwi/gradient.cpp index c161219703..e81069d442 100644 --- a/src/dwi/gradient.cpp +++ b/src/dwi/gradient.cpp @@ -187,6 +187,15 @@ namespace MR + void clear_DW_scheme (Header& header) + { + auto it = header.keyval().find ("dw_scheme"); + if (it != header.keyval().end()) + header.keyval().erase (it); + } + + + Eigen::MatrixXd get_DW_scheme (const Header& header) { DEBUG ("searching for suitable gradient encoding..."); diff --git a/src/dwi/gradient.h b/src/dwi/gradient.h index fc3aa4ec1a..a367f1fe27 100644 --- a/src/dwi/gradient.h +++ b/src/dwi/gradient.h @@ -140,7 +140,9 @@ namespace MR void set_DW_scheme (Header& header, const MatrixType& G) { if (!G.rows()) { - header.keyval().erase ("dw_scheme"); + auto it = header.keyval().find ("dw_scheme"); + if (it != header.keyval().end()) + header.keyval().erase (it); return; } std::string dw_scheme; @@ -183,11 +185,17 @@ namespace MR { set_DW_scheme (header, grad); auto dw_scheme = header.keyval().find ("dw_scheme"); - header.keyval()["prior_dw_scheme"] = dw_scheme->second; - header.keyval().erase (dw_scheme); + if (dw_scheme != header.keyval().end()) { + header.keyval()["prior_dw_scheme"] = dw_scheme->second; + header.keyval().erase (dw_scheme); + } } + //! clear any DW gradient encoding scheme from the header + void clear_DW_scheme (Header&); + + //! get the DW gradient encoding matrix From 36862269c67e544202a8b2f571b67291932b880a Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Mon, 17 Jul 2017 17:55:44 +0100 Subject: [PATCH 092/139] image_diff: relax tolerance on vox spacing Otherwise can get failure due to rounding errors, etc. --- core/image_diff.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/image_diff.h b/core/image_diff.h index 0cb62ec979..afc703ea4c 100644 --- a/core/image_diff.h +++ b/core/image_diff.h @@ -28,7 +28,7 @@ namespace MR 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)) + if (std::abs ((in1.spacing(i) - in2.spacing(i)) / (in1.spacing(i) + in2.spacing(i))) > 1e-4) throw Exception ("images \"" + in1.name() + "\" and \"" + in2.name() + "\" do not have matching voxel spacings " + str(in1.spacing(i)) + " vs " + str(in2.spacing(i))); } From 3c474c656bfb805705c856ef457a7efd034cbf77 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Tue, 18 Jul 2017 15:23:10 +0100 Subject: [PATCH 093/139] DICOM: use a frame from the correct image type to gather info --- core/file/dicom/mapper.cpp | 68 +++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/core/file/dicom/mapper.cpp b/core/file/dicom/mapper.cpp index 8a721af109..9b354617e8 100644 --- a/core/file/dicom/mapper.cpp +++ b/core/file/dicom/mapper.cpp @@ -113,11 +113,12 @@ namespace MR { } const Image& image (*(*series[0])[0]); + const Frame& frame (*frames[0]); - if (std::isfinite (image.echo_time)) - H.keyval()["EchoTime"] = str (0.001 * image.echo_time, 6); + if (std::isfinite (frame.echo_time)) + H.keyval()["EchoTime"] = str (0.001 * frame.echo_time, 6); - size_t nchannels = image.frames.size() ? 1 : image.data_size / (image.dim[0] * image.dim[1] * (image.bits_alloc/8)); + size_t nchannels = frames.size() ? 1 : frame.data_size / (frame.dim[0] * frame.dim[1] * (frame.bits_alloc/8)); if (nchannels > 1) INFO ("data segment is larger than expected from image dimensions - interpreting as multi-channel data"); @@ -132,12 +133,12 @@ namespace MR { } H.stride(0) = ++current_axis; - H.size(0) = image.dim[0]; - H.spacing(0) = image.pixel_size[0]; + H.size(0) = frame.dim[0]; + H.spacing(0) = frame.pixel_size[0]; H.stride(1) = ++current_axis; - H.size(1) = image.dim[1]; - H.spacing(1) = image.pixel_size[1]; + H.size(1) = frame.dim[1]; + H.spacing(1) = frame.pixel_size[1]; H.stride(2) = ++current_axis; H.size(2) = dim[1]; @@ -150,42 +151,41 @@ namespace MR { } - if (image.bits_alloc == 8) + if (frame.bits_alloc == 8) H.datatype() = DataType::UInt8; - else if (image.bits_alloc == 16) { + else if (frame.bits_alloc == 16) { H.datatype() = DataType::UInt16; if (image.is_BE) H.datatype() = DataType::UInt16 | DataType::BigEndian; else H.datatype() = DataType::UInt16 | DataType::LittleEndian; } - else throw Exception ("unexpected number of allocated bits per pixel (" + str (image.bits_alloc) + else throw Exception ("unexpected number of allocated bits per pixel (" + str (frame.bits_alloc) + ") in file \"" + H.name() + "\""); - H.set_intensity_scaling (image.scale_slope, image.scale_intercept); + H.set_intensity_scaling (frame.scale_slope, frame.scale_intercept); // If multi-frame, take the transform information from the sorted frames; the first entry in the // vector should be the first slice of the first volume { transform_type M; - const Frame* frame (image.frames.size() ? image.frames[0].get() : &static_cast (image)); - M(0,0) = -frame->orientation_x[0]; - M(1,0) = -frame->orientation_x[1]; - M(2,0) = +frame->orientation_x[2]; + M(0,0) = -frame.orientation_x[0]; + M(1,0) = -frame.orientation_x[1]; + M(2,0) = +frame.orientation_x[2]; - M(0,1) = -frame->orientation_y[0]; - M(1,1) = -frame->orientation_y[1]; - M(2,1) = +frame->orientation_y[2]; + M(0,1) = -frame.orientation_y[0]; + M(1,1) = -frame.orientation_y[1]; + M(2,1) = +frame.orientation_y[2]; - M(0,2) = -frame->orientation_z[0]; - M(1,2) = -frame->orientation_z[1]; - M(2,2) = +frame->orientation_z[2]; + M(0,2) = -frame.orientation_z[0]; + M(1,2) = -frame.orientation_z[1]; + M(2,2) = +frame.orientation_z[2]; - M(0,3) = -frame->position_vector[0]; - M(1,3) = -frame->position_vector[1]; - M(2,3) = +frame->position_vector[2]; + M(0,3) = -frame.position_vector[0]; + M(1,3) = -frame.position_vector[1]; + M(2,3) = +frame.position_vector[2]; H.transform() = M; std::string dw_scheme = Frame::get_DW_scheme (frames, dim[1], M); @@ -206,24 +206,24 @@ namespace MR { if (H.size (2) != 1) throw Exception ("DICOM mosaic contains multiple slices in image \"" + H.name() + "\""); - H.size(0) = image.acq_dim[0]; - H.size(1) = image.acq_dim[1]; + H.size(0) = frame.acq_dim[0]; + H.size(1) = frame.acq_dim[1]; H.size(2) = image.images_in_mosaic; - if (image.dim[0] % image.acq_dim[0] || image.dim[1] % image.acq_dim[1]) { - WARN ("acquisition matrix [ " + str (image.acq_dim[0]) + " " + str (image.acq_dim[1]) - + " ] does not fit into DICOM mosaic [ " + str (image.dim[0]) + " " + str (image.dim[1]) + if (frame.dim[0] % frame.acq_dim[0] || frame.dim[1] % frame.acq_dim[1]) { + WARN ("acquisition matrix [ " + str (frame.acq_dim[0]) + " " + str (frame.acq_dim[1]) + + " ] does not fit into DICOM mosaic [ " + str (frame.dim[0]) + " " + str (frame.dim[1]) + " ] - adjusting matrix size to suit"); - H.size(0) = image.dim[0] / size_t (float(image.dim[0]) / float(image.acq_dim[0])); - H.size(1) = image.dim[1] / size_t (float(image.dim[1]) / float(image.acq_dim[1])); + H.size(0) = frame.dim[0] / size_t (float(frame.dim[0]) / float(frame.acq_dim[0])); + H.size(1) = frame.dim[1] / size_t (float(frame.dim[1]) / float(frame.acq_dim[1])); } - float xinc = H.spacing(0) * (image.dim[0] - H.size(0)) / 2.0; - float yinc = H.spacing(1) * (image.dim[1] - H.size(1)) / 2.0; + float xinc = H.spacing(0) * (frame.dim[0] - H.size(0)) / 2.0; + float yinc = H.spacing(1) * (frame.dim[1] - H.size(1)) / 2.0; for (size_t i = 0; i < 3; i++) H.transform()(i,3) += xinc * H.transform()(i,0) + yinc * H.transform()(i,1); - io_handler.reset (new MR::ImageIO::Mosaic (H, image.dim[0], image.dim[1], H.size (0), H.size (1), H.size (2))); + io_handler.reset (new MR::ImageIO::Mosaic (H, frame.dim[0], frame.dim[1], H.size (0), H.size (1), H.size (2))); } else io_handler.reset (new MR::ImageIO::Default (H)); From 335038bda6c73566eb937d473e4cef4eebae4f37 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Tue, 18 Jul 2017 15:58:25 +0100 Subject: [PATCH 094/139] DICOM: fix handling of multi-channel data --- core/file/dicom/mapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/file/dicom/mapper.cpp b/core/file/dicom/mapper.cpp index 9b354617e8..8b66b56aeb 100644 --- a/core/file/dicom/mapper.cpp +++ b/core/file/dicom/mapper.cpp @@ -118,7 +118,7 @@ namespace MR { if (std::isfinite (frame.echo_time)) H.keyval()["EchoTime"] = str (0.001 * frame.echo_time, 6); - size_t nchannels = frames.size() ? 1 : frame.data_size / (frame.dim[0] * frame.dim[1] * (frame.bits_alloc/8)); + size_t nchannels = image.frames.size() ? 1 : image.data_size / (frame.dim[0] * frame.dim[1] * (frame.bits_alloc/8)); if (nchannels > 1) INFO ("data segment is larger than expected from image dimensions - interpreting as multi-channel data"); From 0a08cb41c0994007c9a68e85fc7436c192dac1d4 Mon Sep 17 00:00:00 2001 From: Max Pietsch Date: Wed, 5 Jul 2017 15:17:42 +0100 Subject: [PATCH 095/139] population_template: voxel size option --- bin/population_template | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/bin/population_template b/bin/population_template index 82cb2cbd8c..8989bcc99b 100755 --- a/bin/population_template +++ b/bin/population_template @@ -188,6 +188,7 @@ nloptions.add_argument('-nl_grad_step', default='0.5', help='The gradient step s options = app.cmdline.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('-voxel_size', help='Define the template voxel size in mm. Use either a single value for isotropic voxels or 3 comma separated values.') 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') @@ -216,6 +217,18 @@ if len(inFiles) <= 1: else: app.console('Generating a population-average template from ' + str(len(inFiles)) + ' input images') +voxel_size = None +if app.args.voxel_size: + voxel_size = app.args.voxel_size.split() + if len(voxel_size) == 1: + voxel_size = voxel_size * 3 + try: + if len(voxel_size) != 3: + raise + [float(v) for v in voxel_size] + except: + app.error('voxel size needs to be a single or three comma separated floating point numbers, received: '+str(app.args.voxel_size)) + initial_alignment = app.args.initial_alignment if initial_alignment not in ["mass", "geometric", "none"]: message.error('initial_alignment must be one of ' + " ".join(["mass", "geometric", "none"])); @@ -428,7 +441,10 @@ app.console('Generating initial template') input_filenames = [] for i in input: input_filenames.append (abspath(i.directory, i.filename)); -run.command('mraverageheader ' + ' '.join(input_filenames) + ' average_header.mif' ' -fill') +if voxel_size is None: + run.command('mraverageheader ' + ' '.join(input_filenames) + ' average_header.mif -fill') +else: + run.command('mraverageheader -fill ' + ' '.join(input_filenames) + ' - | mrresize - -voxel '+','.join(voxel_size)+' average_header.mif') # crop average space to extent defined by original masks if useMasks: @@ -480,7 +496,10 @@ else: ' ' + os.path.join('masks_transformed', i.prefix + '_translated.mif')) # update average space to new extent run.command('mraverageheader ' + ' '.join([os.path.join('input_transformed', i.prefix + '_translated.mif') for i in input]) + ' average_header_tight.mif') - run.command('mrpad -uniform 10 average_header_tight.mif average_header.mif -force') + if voxel_size is None: + run.command('mrpad -uniform 10 average_header_tight.mif average_header.mif -force') + else: + run.command('mrpad -uniform 10 average_header_tight.mif - | mrresize - -voxel '+','.join(voxel_size)+' average_header.mif -force') run.function(remove, 'average_header_tight.mif') if useMasks: # reslice masks From cea6bad75cc4a1be12e20fae2a458b344f30478d Mon Sep 17 00:00:00 2001 From: Max Pietsch Date: Wed, 19 Jul 2017 10:37:12 +0100 Subject: [PATCH 096/139] docs update --- docs/reference/config_file_options.rst | 190 ++++++++++++++++++ .../reference/scripts/population_template.rst | 2 + 2 files changed, 192 insertions(+) diff --git a/docs/reference/config_file_options.rst b/docs/reference/config_file_options.rst index a0e170c8df..3789b48360 100644 --- a/docs/reference/config_file_options.rst +++ b/docs/reference/config_file_options.rst @@ -9,6 +9,16 @@ List of MRtrix3 configuration file options The default intensity for the ambient light in OpenGL renders. +* **AmbientIntensity** + *default: 0.6* + + The default intensity for the ambient light in OpenGL renders + +* **Analyse.LeftToRight** + *default: 0 (false)* + + A boolean value to indicate whether images in Analyse format should be assumed to be in LAS orientation (default) or RAS (when this is option is turned on). + * **AnalyseLeftToRight** *default: 0 (false)* @@ -19,11 +29,31 @@ List of MRtrix3 configuration file options Specifies whether the b-values should be scaled by the squared norm of the gradient vectors when loading a DW gradient scheme. This is commonly required to correctly interpret images acquired on scanners that nominally only allow a single b-value, as the common workaround is to scale the gradient vectors to modulate the actual b-value. +* **BValueScaling** + *default: 1 (true)* + + specifies whether the b-values should be scaled by the squared norm of the gradient vectors when loading a DW gradient scheme. This is commonly required to correctly interpret images acquired on scanners that nominally only allow a single b-value, as the common workaround is to scale the gradient vectors to modulate the actual b-value. + +* **BValueScaling** + *default: yes* + + specifies whether b-values should be scaled according the DW gradient amplitudes - see the -bvalue_scaling option for details. + * **BZeroThreshold** *default: 10.0* Specifies the b-value threshold for determining those image volumes that correspond to b=0. +* **BZeroThreshold** + *default: 10.0* + + specifies the b-value threshold for determining those image volumes that correspond to b=0 + +* **BackgroundColor** + *default: 1,1,1 (white)* + + The default colour to use for the background in OpenGL panels, notably the SH viewer. + * **BackgroundColor** *default: 1.0,1.0,1.0* @@ -169,6 +199,11 @@ List of MRtrix3 configuration file options Whether or not nodes are forced to be visible when selected. +* **DiffuseIntensity** + *default: 0.3* + + The default intensity for the diffuse light in OpenGL renders + * **DiffuseIntensity** *default: 0.5* @@ -179,16 +214,36 @@ List of MRtrix3 configuration file options A boolean value specifying whether MRtrix applications should abort as soon as any (otherwise non-fatal) warning is issued. +* **FailOnWarn** + *default: 0 (false)* + + A boolean value specifying whether MRtrix applications should abort as soon as any (otherwise non-fatal) warning is issued. + * **HelpCommand** *default: less* The command to use to display each command's help page (leave empty to send directly to the terminal). +* **HelpCommand** + *default: less* + + the command to use to display each command's help page (leave empty to send directly to the terminal). + +* **IconSize** + *default: 24* + + The size of the icons in the main MRView toolbar. + * **IconSize** *default: 30* The size of the icons in the main MRView toolbar. +* **ImageBackgroundColour** + *default: 0,0,0 (black)* + + The default image background colour + * **ImageInterpolation** *default: true* @@ -204,6 +259,16 @@ List of MRtrix3 configuration file options The starting position of the MRView toolbar. Valid values are: top, bottom, left, right. +* **InitialToolBarPosition** + *default: top* + + The starting position of the MRView toolbar. Valid values are: top, bottom, left, right. + +* **LightPosition** + *default: 1,1,3* + + The default position vector to use for the light in OpenGL renders + * **LightPosition** *default: 1.0,1.0,3.0* @@ -214,6 +279,16 @@ List of MRtrix3 configuration file options The height of the colourbar in MRView, in pixels. +* **MRViewColourBarHeight** + *default: 100* + + The height of the colourbar in MRView, in pixels. + +* **MRViewColourBarInset** + *default: 20* + + How far away from the edge of the main window to place the colourbar in MRView, in pixels. + * **MRViewColourBarInset** *default: 20* @@ -224,6 +299,16 @@ List of MRtrix3 configuration file options The position of the colourbar within the main window in MRView. Valid values are: bottomleft, bottomright, topleft, topright. +* **MRViewColourBarPosition** + *default: bottomright* + + The position of the colourbar within the main window in MRView. Valid values are: bottomleft, bottomright, topleft, topright. + +* **MRViewColourBarTextOffset** + *default: 10* + + How far away from the colourbar to place the associated text, in pixels. + * **MRViewColourBarTextOffset** *default: 10* @@ -234,6 +319,11 @@ List of MRtrix3 configuration file options The width of the colourbar in MRView, in pixels. +* **MRViewColourBarWidth** + *default: 20* + + The width of the colourbar in MRView, in pixels. + * **MRViewColourHorizontalPadding** *default: 100* @@ -244,6 +334,16 @@ List of MRtrix3 configuration file options Whether MRView tools should start docked in the main window, or floating (detached from the main window). +* **MRViewDockFloating** + *default: 0 (false)* + + Whether Tools should start docked in the main window, or floating (detached from the main window). + +* **MRViewFocusModifierKey** + *default: alt (cmd on MacOSX)* + + modifier key to select focus mode in MRView. Valid choices include shift, alt, ctrl, meta (on MacOSX: shift, alt, ctrl, cmd). + * **MRViewFocusModifierKey** *default: meta (cmd on MacOSX)* @@ -269,6 +369,11 @@ List of MRtrix3 configuration file options Modifier key to select move mode in MRView. Valid choices include shift, alt, ctrl, meta (on MacOSX: shift, alt, ctrl, cmd). +* **MRViewMoveModifierKey** + *default: shift* + + modifier key to select move mode in MRView. Valid choices include shift, alt, ctrl, meta (on MacOSX: shift, alt, ctrl, cmd). + * **MRViewOdfScale** *default: 1.0* @@ -279,6 +384,11 @@ List of MRtrix3 configuration file options Modifier key to select rotate mode in MRView. Valid choices include shift, alt, ctrl, meta (on MacOSX: shift, alt, ctrl, cmd). +* **MRViewRotateModifierKey** + *default: ctrl* + + modifier key to select rotate mode in MRView. Valid choices include shift, alt, ctrl, meta (on MacOSX: shift, alt, ctrl, cmd). + * **MRViewShowColourbar** *default: true* @@ -304,6 +414,11 @@ List of MRtrix3 configuration file options Voxel information shown in main image overlay +* **MRViewToolFontSize** + *default: 2 points less than the standard system font* + + The point size for the font to use in MRView Tools. + * **MRViewToolFontSize** *default: 2 points less than the standard system font* @@ -319,6 +434,16 @@ List of MRtrix3 configuration file options How many samples to use for multi-sample anti-aliasing (to improve display quality). +* **MSAA** + *default: 0 (false)* + + How many samples to use for multi-sample anti-aliasing (to improve display quality). + +* **NIFTI.AllowBitwise** + *default: 0 (false)* + + 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.AllowBitwise** *default: 0 (false)* @@ -339,6 +464,11 @@ List of MRtrix3 configuration file options 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: 0 (true on MacOSX, false otherwise)* + + Whether the creation of an OpenGL 3.3 context requires it to be a core profile (needed on newer versions of the ATI drivers on Linux, for instance). + * **NeedOpenGLCoreProfile** *default: 1 (true)* @@ -349,6 +479,16 @@ List of MRtrix3 configuration file options Set the default number of CPU threads to use for multi-threading. +* **NumberOfThreads** + *default: number of threads provided by hardware* + + set the default number of CPU threads to use for multi-threading. + +* **NumberOfUndos** + *default: 16* + + The number of undo operations permitted in the MRView ROI editor tool + * **NumberOfUndos** *default: 16* @@ -359,6 +499,11 @@ List of MRtrix3 configuration file options The default colour to use for objects (i.e. SH glyphs) when not colouring by direction. +* **ObjectColor** + *default: 1,1,0 (yellow)* + + The default colour to use for objects (i.e. SH glyphs) when not colouring by direction. + * **ScriptTmpDir** *default: `.`* @@ -374,11 +519,26 @@ List of MRtrix3 configuration file options Initial buffer size for data in MRtrix sparse image format file (in bytes). +* **SparseDataInitialSize** + *default: 16777216* + + initial buffer size for data in MRtrix sparse image format file (in bytes). + +* **SpecularExponent** + *default: 1* + + The default exponent for the specular light in OpenGL renders + * **SpecularExponent** *default: 5.0* The default exponent for the specular light in OpenGL renders. +* **SpecularIntensity** + *default: 0.4* + + The default intensity for the specular light in OpenGL renders + * **SpecularIntensity** *default: 0.5* @@ -394,6 +554,16 @@ List of MRtrix3 configuration file options A boolean value to indicate whether colours should be used in the terminal. +* **TerminalColor** + *default: 1 (true)* + + A boolean value to indicate whether colours should be used in the terminal. + +* **TmpFileDir** + *default: `/tmp` (on Unix), `.` (on Windows)* + + The prefix for temporary files (as used in pipelines). By default, these files get written to the current folder, which may cause performance issues when operating over distributed file systems. In this case, it may be better to specify `/tmp/` here. + * **TmpFileDir** *default: `/tmp` (on Unix), `.` (on Windows)* @@ -404,6 +574,16 @@ List of MRtrix3 configuration file options The prefix to use for the basename of temporary files. This will be used to generate a unique filename for the temporary file, by adding random characters to this prefix, followed by a suitable suffix (depending on file type). Note that this prefix can also be manipulated using the `MRTRIX_TMPFILE_PREFIX` environment variable, without editing the config file. +* **TmpFilePrefix** + *default: `mrtrix-tmp-`* + + The prefix to use for the basename of temporary files. This will be used to generate a unique filename for the temporary file, by adding random characters to this prefix, followed by a suitable suffix (depending on file type). Note that this prefix can also be manipulated using the `MRTRIX_TMPFILE_PREFIX` environment variable, without editing the config file. + +* **ToolbarStyle** + *default: 2* + + The style of the main toolbar buttons in MRView. See Qt's documentation for Qt::ToolButtonStyle. + * **ToolbarStyle** *default: 2* @@ -414,6 +594,16 @@ List of MRtrix3 configuration file options The size of the write-back buffer (in bytes) to use when writing track files. MRtrix will store the output tracks in a relatively large buffer to limit the number of write() calls, avoid associated issues such as file fragmentation. +* **TrackWriterBufferSize** + *default: 16777216* + + The size of the write-back buffer (in bytes) to use when writing track files. MRtrix will store the output tracks in a relatively large buffer to limit the number of write() calls, avoid associated issues such as file fragmentation. + +* **VSync** + *default: 0 (false)* + + Whether the screen update should synchronise with the monitor's vertical refresh (to avoid tearing artefacts). + * **VSync** *default: 0 (false)* diff --git a/docs/reference/scripts/population_template.rst b/docs/reference/scripts/population_template.rst index c72f878d74..a16dddb286 100644 --- a/docs/reference/scripts/population_template.rst +++ b/docs/reference/scripts/population_template.rst @@ -31,6 +31,8 @@ Input, output and general options - **-type** 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: "rigid_affine", "rigid_nonlinear", "affine_nonlinear", "rigid_affine_nonlinear". Default: rigid_affine_nonlinear +- **-voxel_size** Define the template voxel size in mm. Use either a single value for isotropic voxels or 3 comma separated values. + - **-initial_alignment** Method of alignment to form the initial template. Options are "mass" (default), "geometric" and "none". - **-mask_dir** 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 From 55ab61827778e965703c9c8cb112b2de7ff52847 Mon Sep 17 00:00:00 2001 From: Max Pietsch Date: Wed, 19 Jul 2017 11:34:16 +0100 Subject: [PATCH 097/139] docs: FBA population_template 1.25mm voxel size --- .../common_fba_steps/population_template2.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/fixel_based_analysis/common_fba_steps/population_template2.rst b/docs/fixel_based_analysis/common_fba_steps/population_template2.rst index 45a09279aa..d56206ed51 100644 --- a/docs/fixel_based_analysis/common_fba_steps/population_template2.rst +++ b/docs/fixel_based_analysis/common_fba_steps/population_template2.rst @@ -2,7 +2,7 @@ Run the template building script as follows: .. code-block:: console - $ population_template ../template/fod_input -mask_dir ../template/mask_input ../template/wmfod_template.mif + $ population_template ../template/fod_input -mask_dir ../template/mask_input ../template/wmfod_template.mif -voxel_size 1.25 **If you are building a template from your entire study population**, run the population_template script use the :code:`-warp_dir warps` option to output a @@ -13,6 +13,6 @@ forward and reverse warps (see :ref:`mrregister` for more info). After population template creation is complete, to convert this warp format to a more conventional 4D deformation field format ready for the subsequent steps, run -.. code-block:: console +.. code-block:: console $ foreach ../template/warps/* : warpconvert -type warpfull2deformation -template ../template/wmfod_template.mif IN PRE/subject2template_warp.mif From 05277b99c01efedf1595a7909babfb2c54c0b921 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Wed, 19 Jul 2017 16:33:50 +0100 Subject: [PATCH 098/139] dirgen: use atomic post-increment operator rather than fetch_add() Just because it looks nicer... --- cmd/dirgen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/dirgen.cpp b/cmd/dirgen.cpp index b481ef0479..eaf5823d5a 100644 --- a/cmd/dirgen.cpp +++ b/cmd/dirgen.cpp @@ -177,7 +177,7 @@ class Energy { MEMALIGN(Energy) void execute () { size_t this_start = 0; - while ((this_start = current_start.fetch_add (1)) < restarts) { + while ((this_start = current_start++) < restarts) { INFO ("launching start " + str (this_start)); double E = 0.0; From 13ac06963a17ae12f065f9972df5b089b532295d Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Fri, 14 Jul 2017 18:11:14 +1000 Subject: [PATCH 099/139] mrview: lightbox: Polishing up viewing of volumes When viewing volumes: * Original volume is rendered in the top-left pane * Changing the layout of lightbox keeps the selected slice fixed * Toggling between the lightbox and other modes retains the state of the original top-left volume --- src/gui/mrview/mode/base.cpp | 2 ++ src/gui/mrview/mode/base.h | 1 + src/gui/mrview/mode/lightbox.cpp | 45 +++++++++++++++++++++++--------- src/gui/mrview/mode/lightbox.h | 3 ++- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/gui/mrview/mode/base.cpp b/src/gui/mrview/mode/base.cpp index 2709e2d26a..7814160c4b 100644 --- a/src/gui/mrview/mode/base.cpp +++ b/src/gui/mrview/mode/base.cpp @@ -142,11 +142,13 @@ namespace MR done_painting: update_overlays = false; + finished_paintGL (); ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; } void Base::paint (Projection&) { } + void Base::finished_paintGL () {} void Base::mouse_press_event () { } void Base::mouse_release_event () { } diff --git a/src/gui/mrview/mode/base.h b/src/gui/mrview/mode/base.h index e40c3e1d6b..fcc13280ff 100644 --- a/src/gui/mrview/mode/base.h +++ b/src/gui/mrview/mode/base.h @@ -206,6 +206,7 @@ namespace MR } void reset_view (); + virtual void finished_paintGL (); bool visible; }; diff --git a/src/gui/mrview/mode/lightbox.cpp b/src/gui/mrview/mode/lightbox.cpp index 307315cec1..6f6c5cf7eb 100644 --- a/src/gui/mrview/mode/lightbox.cpp +++ b/src/gui/mrview/mode/lightbox.cpp @@ -39,6 +39,9 @@ namespace MR { Image* img = image(); + if (render_volumes()) + current_slice_index = 0; + if(!img || prev_image_name != img->header().name()) image_changed_event(); else { @@ -100,11 +103,14 @@ namespace MR n_cols * n_rows, proj_focusdelta(projection, 0.f)); - set_current_slice_index((n_rows * n_cols) / 2); - update_slices_focusdelta(); - update_volume_indices(); + size_t slice_idx = render_volumes() ? + std::min(current_slice_index, (n_rows * n_cols) - 1) : (n_rows * n_cols) / 2; + + set_current_slice_index(slice_idx); + update_slices_focusdelta(); + frame_VB.clear(); frame_VAO.clear(); } @@ -114,11 +120,7 @@ namespace MR int prev_index = current_slice_index; current_slice_index = slice_index; - if (render_volumes()) { - window().set_image_volume(3, volume_indices[current_slice_index]); - } - - else if (prev_index != (int)current_slice_index) { + if (!render_volumes() && prev_index != (int)current_slice_index) { const Projection& slice_proj = slices_proj_focusdelta[current_slice_index].first; float focus_delta = slices_proj_focusdelta[current_slice_index].second; @@ -145,11 +147,10 @@ namespace MR if (!is_4d) return; - int n_vols = image()->image.size(3); int initial_vol = image()->image.index(3); for(int i = 0, N = volume_indices.size(); i < N; ++i) - volume_indices[i] = std::min(std::max(initial_vol + (int)volume_increment * (i - (int)current_slice_index), 0), n_vols - 1); + volume_indices[i] = initial_vol + (int)volume_increment * i; } void LightBox::draw_plane_primitive (int axis, Displayable::Shader& shader_program, Projection& with_projection) @@ -161,6 +162,17 @@ namespace MR ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; } + void LightBox::finished_paintGL () + { + // When rendering volumes, the initial state is always considered to be + // the first volume (i.e. top-left slice) + // However, for the purposes of correctly rendering voxel information we need the + // image volume to correspond to the selected volume + // Hence, this is called once paintGL has finished to restore the initial state + if(render_volumes()) + image()->image.index(3) = volume_indices[0]; + } + void LightBox::paint(Projection&) { ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; @@ -182,6 +194,7 @@ namespace MR } bool rend_vols = render_volumes(); + bool render_plane = true; size_t slice_idx = 0; for(size_t row = 0; row < n_rows; ++row) { @@ -195,8 +208,13 @@ namespace MR // because move_in_out_displacement is reliant on MVP setup_projection (plane(), slice_proj); - if (rend_vols) - image()->image.index(3) = volume_indices[slice_idx]; + if (rend_vols) { + int n_vols = image()->image.size(3); + if (volume_indices[slice_idx] >= 0 && volume_indices[slice_idx] < n_vols - 1) + image()->image.index(3) = volume_indices[slice_idx]; + else + render_plane = false; + } else { float focus_delta = slices_proj_focusdelta[slice_idx].second; @@ -204,7 +222,8 @@ namespace MR set_focus(orig_focus + slice_focus); } - draw_plane_primitive(plane(), slice_shader, slice_proj); + if (render_plane) + draw_plane_primitive(plane(), slice_shader, slice_proj); if(slice_idx == current_slice_index) { // Drawing plane may alter the depth test state diff --git a/src/gui/mrview/mode/lightbox.h b/src/gui/mrview/mode/lightbox.h index 094d6665fb..ccc99d437d 100644 --- a/src/gui/mrview/mode/lightbox.h +++ b/src/gui/mrview/mode/lightbox.h @@ -69,6 +69,7 @@ namespace MR protected: void draw_plane_primitive(int axis, Displayable::Shader& shader_program, Projection& with_projection) override; + void finished_paintGL() override; private: static size_t slice_index(size_t row, size_t col) { @@ -93,7 +94,7 @@ namespace MR bool layout_is_dirty; size_t current_slice_index; - vector volume_indices; + vector volume_indices; vector slices_proj_focusdelta; GL::VertexBuffer frame_VB; From 6e2e8a2bf0b95460df42f131b06632406eaa54ac Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Tue, 18 Jul 2017 13:31:09 +1000 Subject: [PATCH 100/139] mrview: lightbox: make sure volume increment value persists even after toggling between view modes --- src/gui/mrview/mode/lightbox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/mrview/mode/lightbox.cpp b/src/gui/mrview/mode/lightbox.cpp index 6f6c5cf7eb..bc2323492b 100644 --- a/src/gui/mrview/mode/lightbox.cpp +++ b/src/gui/mrview/mode/lightbox.cpp @@ -45,7 +45,7 @@ namespace MR if(!img || prev_image_name != img->header().name()) image_changed_event(); else { - set_volume_increment(1); + set_volume_increment(volume_increment); set_slice_increment(slice_focus_increment); } } From 723e0bc1ac91b076121442d1d5fabe19ed401e51 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Thu, 20 Jul 2017 11:13:20 +0100 Subject: [PATCH 101/139] dcminfo: print out all information for specified tag ... rather than just the first entry. --- cmd/dcminfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/dcminfo.cpp b/cmd/dcminfo.cpp index c45e6a8248..0ac2ae7bc6 100644 --- a/cmd/dcminfo.cpp +++ b/cmd/dcminfo.cpp @@ -75,7 +75,7 @@ void run () while (item.read()) { for (size_t n = 0; n < opt.size(); ++n) if (item.is (tags[n].group, tags[n].element)) - tags[n].value = item.get_string()[0]; + tags[n].value = join (item.get_string(), " "); } for (size_t n = 0; n < opt.size(); ++n) From 28828465a68252d8cb5aaa893379ddd93381973a Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Thu, 20 Jul 2017 12:11:57 +0100 Subject: [PATCH 102/139] tensor2metric: sort eigenvalues by absolute values as standard and remove option to influence this, as discussed in #1026 --- cmd/tensor2metric.cpp | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/cmd/tensor2metric.cpp b/cmd/tensor2metric.cpp index 5426042348..dec5a632a6 100644 --- a/cmd/tensor2metric.cpp +++ b/cmd/tensor2metric.cpp @@ -80,10 +80,6 @@ void usage () "principal (1) and minor (3) eigenvalues/eigenvectors (default = 1).") + Argument ("sequence").type_sequence_int() - + Option ("abs", - "sort eigenvalues in order of decreasing absolute value, rather than actual " - "value (the default). This interacts with the -num option above.") - + Option ("modulate", "specify how to modulate the magnitude of the eigenvectors. Valid choices " "are: none, FA, eigval (default = FA).") @@ -119,8 +115,7 @@ class Processor { MEMALIGN(Processor) Image& value_img, Image& vector_img, vector& vals, - int modulate, - bool sort_by_abs) : + int modulate) : mask_img (mask_img), adc_img (adc_img), fa_img (fa_img), @@ -132,8 +127,7 @@ class Processor { MEMALIGN(Processor) value_img (value_img), vector_img (vector_img), vals (vals), - modulate (modulate), - sort_by_abs (sort_by_abs) { + modulate (modulate) { for (auto& n : this->vals) --n; } @@ -168,7 +162,7 @@ class Processor { MEMALIGN(Processor) fa_img.value() = fa; } - bool need_eigenvalues = value_img.valid() || (vector_img.valid() && (modulate == 2 || sort_by_abs)) || ad_img.valid() || rd_img.valid() || cl_img.valid() || cp_img.valid() || cs_img.valid(); + bool need_eigenvalues = value_img.valid() || vector_img.valid() || ad_img.valid() || rd_img.valid() || cl_img.valid() || cp_img.valid() || cs_img.valid(); Eigen::SelfAdjointEigenSolver es; if (need_eigenvalues || vector_img.valid()) { @@ -186,11 +180,9 @@ class Processor { MEMALIGN(Processor) ssize_t ith_eig[3] = { 2, 1, 0 }; if (need_eigenvalues) { eigval = es.eigenvalues(); - if (sort_by_abs) { - ith_eig[0] = 0; ith_eig[1] = 1; ith_eig[2] = 2; - std::sort (std::begin (ith_eig), std::end (ith_eig), - [&eigval](size_t a, size_t b) { return std::abs(eigval[a]) > std::abs(eigval[b]); }); - } + ith_eig[0] = 0; ith_eig[1] = 1; ith_eig[2] = 2; + std::sort (std::begin (ith_eig), std::end (ith_eig), + [&eigval](size_t a, size_t b) { return std::abs(eigval[a]) > std::abs(eigval[b]); }); } /* output value */ @@ -268,7 +260,6 @@ class Processor { MEMALIGN(Processor) Image vector_img; vector vals; int modulate; - bool sort_by_abs; }; @@ -350,8 +341,6 @@ void run () throw Exception ("eigenvalue/eigenvector number is out of bounds"); } - bool sort_by_abs = get_options ("abs").size(); - float modulate = get_option_value ("modulate", 1); auto value_img = Image(); @@ -374,5 +363,5 @@ void run () } ThreadedLoop ("computing metrics", dt_img, 0, 3) - .run (Processor (mask_img, adc_img, fa_img, ad_img, rd_img, cl_img, cp_img, cs_img, value_img, vector_img, vals, modulate, sort_by_abs), dt_img); + .run (Processor (mask_img, adc_img, fa_img, ad_img, rd_img, cl_img, cp_img, cs_img, value_img, vector_img, vals, modulate), dt_img); } From 07ce91572a034aa5e06c8f47b2b945285dc2c10f Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Thu, 20 Jul 2017 14:20:39 +0100 Subject: [PATCH 103/139] write_mrtrix: update to output generic fields as discussed on #1005 --- matlab/write_mrtrix.m | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/matlab/write_mrtrix.m b/matlab/write_mrtrix.m index 5b4bdca7da..5ef9bba830 100644 --- a/matlab/write_mrtrix.m +++ b/matlab/write_mrtrix.m @@ -82,12 +82,6 @@ function write_mrtrix (image, filename) fprintf (fid, '\nmrtrix_version: %s', 'matlab'); -if isstruct (image) && isfield (image, 'comments') - for i=1:numel(image.comments) - fprintf (fid, '\ncomments: %s', image.comments{i}); - end -end - if isstruct (image) && isfield (image, 'transform') fprintf (fid, '\ntransform: %.6f', image.transform(1,1)); fprintf (fid, ',%.6f', image.transform(1,2:4)); @@ -104,6 +98,32 @@ function write_mrtrix (image, filename) end end +% write out any other fields: +if isstruct (image) + f = fieldnames(image); + % don't worry about fields that have already been dealt with: + f(strcmp(f,'dim')) = []; + f(strcmp(f,'vox')) = []; + f(strcmp(f,'layout')) = []; + f(strcmp(f,'datatype')) = []; + f(strcmp(f,'transform')) = []; + f(strcmp(f,'dw_scheme')) = []; + f(strcmp(f,'data')) = []; + + % write out contents of the remainging fields: + for n = 1:numel(f) + val = getfield (image, f{n}); + if iscell (val) + for i=1:numel(val) + fprintf (fid, '\n%s: %s', f{n}, val{i}); + end + else + fprintf (fid, '\n%s: %s', f{n}, val); + end + end +end + + if strcmp(filename(end-3:end), '.mif') datafile = filename; dataoffset = ftell (fid) + 24; From ab3f4c3a6044a7ceb6da2be434c2c7c189b15c21 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 21 Jul 2017 10:47:17 +1000 Subject: [PATCH 104/139] tensor2metric: Error if no output specified --- cmd/tensor2metric.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/cmd/tensor2metric.cpp b/cmd/tensor2metric.cpp index dec5a632a6..c007aed420 100644 --- a/cmd/tensor2metric.cpp +++ b/cmd/tensor2metric.cpp @@ -280,12 +280,15 @@ void run () mask_img = Image::open (opt[0][0]); check_dimensions (dt_img, mask_img, 0, 3); } + + size_t metric_count = 0; auto adc_img = Image(); opt = get_options ("adc"); if (opt.size()) { header.ndim() = 3; adc_img = Image::create (opt[0][0], header); + metric_count++; } auto fa_img = Image(); @@ -293,6 +296,7 @@ void run () if (opt.size()) { header.ndim() = 3; fa_img = Image::create (opt[0][0], header); + metric_count++; } auto ad_img = Image(); @@ -300,6 +304,7 @@ void run () if (opt.size()) { header.ndim() = 3; ad_img = Image::create (opt[0][0], header); + metric_count++; } auto rd_img = Image(); @@ -307,6 +312,7 @@ void run () if (opt.size()) { header.ndim() = 3; rd_img = Image::create (opt[0][0], header); + metric_count++; } auto cl_img = Image(); @@ -314,6 +320,7 @@ void run () if (opt.size()) { header.ndim() = 3; cl_img = Image::create (opt[0][0], header); + metric_count++; } auto cp_img = Image(); @@ -321,6 +328,7 @@ void run () if (opt.size()) { header.ndim() = 3; cp_img = Image::create (opt[0][0], header); + metric_count++; } auto cs_img = Image(); @@ -328,6 +336,7 @@ void run () if (opt.size()) { header.ndim() = 3; cs_img = Image::create (opt[0][0], header); + metric_count++; } vector vals = {1}; @@ -352,6 +361,7 @@ void run () header.size (3) = vals.size(); } value_img = Image::create (opt[0][0], header); + metric_count++; } auto vector_img = Image(); @@ -360,8 +370,12 @@ void run () header.ndim() = 4; header.size (3) = vals.size()*3; vector_img = Image::create (opt[0][0], header); + metric_count++; } + + if (!metric_count) + throw Exception ("No output specified; must request at least one metric of interest using the available command-line options"); - ThreadedLoop ("computing metrics", dt_img, 0, 3) + ThreadedLoop (std::string("computing metric") + (metric_count > 1 ? "s" : ""), dt_img, 0, 3) .run (Processor (mask_img, adc_img, fa_img, ad_img, rd_img, cl_img, cp_img, cs_img, value_img, vector_img, vals, modulate), dt_img); } From 60a3ceaad5fec23dd8ad79515906f8dc63991f15 Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Fri, 21 Jul 2017 10:57:05 +1000 Subject: [PATCH 105/139] mrview: view: Renaming lightbox volume checkbox --- src/gui/mrview/tool/view.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/mrview/tool/view.cpp b/src/gui/mrview/tool/view.cpp index 27d79a67f6..d244ef93ed 100644 --- a/src/gui/mrview/tool/view.cpp +++ b/src/gui/mrview/tool/view.cpp @@ -1012,7 +1012,7 @@ namespace MR grid_layout->addWidget (new QLabel (tr("Columns:")), 3, 0); grid_layout->addWidget(light_box_cols, 3, 2); - light_box_show_4d = new QCheckBox(tr("Show 4d volumes"), this); + light_box_show_4d = new QCheckBox(tr("Cycle through volumes"), this); grid_layout->addWidget(light_box_show_4d, 4, 0, 1, 2); light_box_show_grid = new QCheckBox(tr("Show grid"), this); From b2a0e54cc92a97f8ec151867196c65990aeda522 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Fri, 21 Jul 2017 12:21:28 +1000 Subject: [PATCH 106/139] docs update --- docs/reference/config_file_options.rst | 190 ------------------------- 1 file changed, 190 deletions(-) diff --git a/docs/reference/config_file_options.rst b/docs/reference/config_file_options.rst index 3789b48360..a0e170c8df 100644 --- a/docs/reference/config_file_options.rst +++ b/docs/reference/config_file_options.rst @@ -9,16 +9,6 @@ List of MRtrix3 configuration file options The default intensity for the ambient light in OpenGL renders. -* **AmbientIntensity** - *default: 0.6* - - The default intensity for the ambient light in OpenGL renders - -* **Analyse.LeftToRight** - *default: 0 (false)* - - A boolean value to indicate whether images in Analyse format should be assumed to be in LAS orientation (default) or RAS (when this is option is turned on). - * **AnalyseLeftToRight** *default: 0 (false)* @@ -29,31 +19,11 @@ List of MRtrix3 configuration file options Specifies whether the b-values should be scaled by the squared norm of the gradient vectors when loading a DW gradient scheme. This is commonly required to correctly interpret images acquired on scanners that nominally only allow a single b-value, as the common workaround is to scale the gradient vectors to modulate the actual b-value. -* **BValueScaling** - *default: 1 (true)* - - specifies whether the b-values should be scaled by the squared norm of the gradient vectors when loading a DW gradient scheme. This is commonly required to correctly interpret images acquired on scanners that nominally only allow a single b-value, as the common workaround is to scale the gradient vectors to modulate the actual b-value. - -* **BValueScaling** - *default: yes* - - specifies whether b-values should be scaled according the DW gradient amplitudes - see the -bvalue_scaling option for details. - * **BZeroThreshold** *default: 10.0* Specifies the b-value threshold for determining those image volumes that correspond to b=0. -* **BZeroThreshold** - *default: 10.0* - - specifies the b-value threshold for determining those image volumes that correspond to b=0 - -* **BackgroundColor** - *default: 1,1,1 (white)* - - The default colour to use for the background in OpenGL panels, notably the SH viewer. - * **BackgroundColor** *default: 1.0,1.0,1.0* @@ -199,11 +169,6 @@ List of MRtrix3 configuration file options Whether or not nodes are forced to be visible when selected. -* **DiffuseIntensity** - *default: 0.3* - - The default intensity for the diffuse light in OpenGL renders - * **DiffuseIntensity** *default: 0.5* @@ -214,36 +179,16 @@ List of MRtrix3 configuration file options A boolean value specifying whether MRtrix applications should abort as soon as any (otherwise non-fatal) warning is issued. -* **FailOnWarn** - *default: 0 (false)* - - A boolean value specifying whether MRtrix applications should abort as soon as any (otherwise non-fatal) warning is issued. - * **HelpCommand** *default: less* The command to use to display each command's help page (leave empty to send directly to the terminal). -* **HelpCommand** - *default: less* - - the command to use to display each command's help page (leave empty to send directly to the terminal). - -* **IconSize** - *default: 24* - - The size of the icons in the main MRView toolbar. - * **IconSize** *default: 30* The size of the icons in the main MRView toolbar. -* **ImageBackgroundColour** - *default: 0,0,0 (black)* - - The default image background colour - * **ImageInterpolation** *default: true* @@ -259,16 +204,6 @@ List of MRtrix3 configuration file options The starting position of the MRView toolbar. Valid values are: top, bottom, left, right. -* **InitialToolBarPosition** - *default: top* - - The starting position of the MRView toolbar. Valid values are: top, bottom, left, right. - -* **LightPosition** - *default: 1,1,3* - - The default position vector to use for the light in OpenGL renders - * **LightPosition** *default: 1.0,1.0,3.0* @@ -279,16 +214,6 @@ List of MRtrix3 configuration file options The height of the colourbar in MRView, in pixels. -* **MRViewColourBarHeight** - *default: 100* - - The height of the colourbar in MRView, in pixels. - -* **MRViewColourBarInset** - *default: 20* - - How far away from the edge of the main window to place the colourbar in MRView, in pixels. - * **MRViewColourBarInset** *default: 20* @@ -299,16 +224,6 @@ List of MRtrix3 configuration file options The position of the colourbar within the main window in MRView. Valid values are: bottomleft, bottomright, topleft, topright. -* **MRViewColourBarPosition** - *default: bottomright* - - The position of the colourbar within the main window in MRView. Valid values are: bottomleft, bottomright, topleft, topright. - -* **MRViewColourBarTextOffset** - *default: 10* - - How far away from the colourbar to place the associated text, in pixels. - * **MRViewColourBarTextOffset** *default: 10* @@ -319,11 +234,6 @@ List of MRtrix3 configuration file options The width of the colourbar in MRView, in pixels. -* **MRViewColourBarWidth** - *default: 20* - - The width of the colourbar in MRView, in pixels. - * **MRViewColourHorizontalPadding** *default: 100* @@ -334,16 +244,6 @@ List of MRtrix3 configuration file options Whether MRView tools should start docked in the main window, or floating (detached from the main window). -* **MRViewDockFloating** - *default: 0 (false)* - - Whether Tools should start docked in the main window, or floating (detached from the main window). - -* **MRViewFocusModifierKey** - *default: alt (cmd on MacOSX)* - - modifier key to select focus mode in MRView. Valid choices include shift, alt, ctrl, meta (on MacOSX: shift, alt, ctrl, cmd). - * **MRViewFocusModifierKey** *default: meta (cmd on MacOSX)* @@ -369,11 +269,6 @@ List of MRtrix3 configuration file options Modifier key to select move mode in MRView. Valid choices include shift, alt, ctrl, meta (on MacOSX: shift, alt, ctrl, cmd). -* **MRViewMoveModifierKey** - *default: shift* - - modifier key to select move mode in MRView. Valid choices include shift, alt, ctrl, meta (on MacOSX: shift, alt, ctrl, cmd). - * **MRViewOdfScale** *default: 1.0* @@ -384,11 +279,6 @@ List of MRtrix3 configuration file options Modifier key to select rotate mode in MRView. Valid choices include shift, alt, ctrl, meta (on MacOSX: shift, alt, ctrl, cmd). -* **MRViewRotateModifierKey** - *default: ctrl* - - modifier key to select rotate mode in MRView. Valid choices include shift, alt, ctrl, meta (on MacOSX: shift, alt, ctrl, cmd). - * **MRViewShowColourbar** *default: true* @@ -414,11 +304,6 @@ List of MRtrix3 configuration file options Voxel information shown in main image overlay -* **MRViewToolFontSize** - *default: 2 points less than the standard system font* - - The point size for the font to use in MRView Tools. - * **MRViewToolFontSize** *default: 2 points less than the standard system font* @@ -434,16 +319,6 @@ List of MRtrix3 configuration file options How many samples to use for multi-sample anti-aliasing (to improve display quality). -* **MSAA** - *default: 0 (false)* - - How many samples to use for multi-sample anti-aliasing (to improve display quality). - -* **NIFTI.AllowBitwise** - *default: 0 (false)* - - 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.AllowBitwise** *default: 0 (false)* @@ -464,11 +339,6 @@ List of MRtrix3 configuration file options 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: 0 (true on MacOSX, false otherwise)* - - Whether the creation of an OpenGL 3.3 context requires it to be a core profile (needed on newer versions of the ATI drivers on Linux, for instance). - * **NeedOpenGLCoreProfile** *default: 1 (true)* @@ -479,16 +349,6 @@ List of MRtrix3 configuration file options Set the default number of CPU threads to use for multi-threading. -* **NumberOfThreads** - *default: number of threads provided by hardware* - - set the default number of CPU threads to use for multi-threading. - -* **NumberOfUndos** - *default: 16* - - The number of undo operations permitted in the MRView ROI editor tool - * **NumberOfUndos** *default: 16* @@ -499,11 +359,6 @@ List of MRtrix3 configuration file options The default colour to use for objects (i.e. SH glyphs) when not colouring by direction. -* **ObjectColor** - *default: 1,1,0 (yellow)* - - The default colour to use for objects (i.e. SH glyphs) when not colouring by direction. - * **ScriptTmpDir** *default: `.`* @@ -519,26 +374,11 @@ List of MRtrix3 configuration file options Initial buffer size for data in MRtrix sparse image format file (in bytes). -* **SparseDataInitialSize** - *default: 16777216* - - initial buffer size for data in MRtrix sparse image format file (in bytes). - -* **SpecularExponent** - *default: 1* - - The default exponent for the specular light in OpenGL renders - * **SpecularExponent** *default: 5.0* The default exponent for the specular light in OpenGL renders. -* **SpecularIntensity** - *default: 0.4* - - The default intensity for the specular light in OpenGL renders - * **SpecularIntensity** *default: 0.5* @@ -554,16 +394,6 @@ List of MRtrix3 configuration file options A boolean value to indicate whether colours should be used in the terminal. -* **TerminalColor** - *default: 1 (true)* - - A boolean value to indicate whether colours should be used in the terminal. - -* **TmpFileDir** - *default: `/tmp` (on Unix), `.` (on Windows)* - - The prefix for temporary files (as used in pipelines). By default, these files get written to the current folder, which may cause performance issues when operating over distributed file systems. In this case, it may be better to specify `/tmp/` here. - * **TmpFileDir** *default: `/tmp` (on Unix), `.` (on Windows)* @@ -574,16 +404,6 @@ List of MRtrix3 configuration file options The prefix to use for the basename of temporary files. This will be used to generate a unique filename for the temporary file, by adding random characters to this prefix, followed by a suitable suffix (depending on file type). Note that this prefix can also be manipulated using the `MRTRIX_TMPFILE_PREFIX` environment variable, without editing the config file. -* **TmpFilePrefix** - *default: `mrtrix-tmp-`* - - The prefix to use for the basename of temporary files. This will be used to generate a unique filename for the temporary file, by adding random characters to this prefix, followed by a suitable suffix (depending on file type). Note that this prefix can also be manipulated using the `MRTRIX_TMPFILE_PREFIX` environment variable, without editing the config file. - -* **ToolbarStyle** - *default: 2* - - The style of the main toolbar buttons in MRView. See Qt's documentation for Qt::ToolButtonStyle. - * **ToolbarStyle** *default: 2* @@ -594,16 +414,6 @@ List of MRtrix3 configuration file options The size of the write-back buffer (in bytes) to use when writing track files. MRtrix will store the output tracks in a relatively large buffer to limit the number of write() calls, avoid associated issues such as file fragmentation. -* **TrackWriterBufferSize** - *default: 16777216* - - The size of the write-back buffer (in bytes) to use when writing track files. MRtrix will store the output tracks in a relatively large buffer to limit the number of write() calls, avoid associated issues such as file fragmentation. - -* **VSync** - *default: 0 (false)* - - Whether the screen update should synchronise with the monitor's vertical refresh (to avoid tearing artefacts). - * **VSync** *default: 0 (false)* From 3c943e188c5c7fce4f70a990570fcd74ac6387b3 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Fri, 21 Jul 2017 13:02:43 +1000 Subject: [PATCH 107/139] copyrights update --- core/file/tiff.cpp | 19 +++++++++---------- core/file/tiff.h | 19 +++++++++---------- core/formats/tiff.cpp | 20 ++++++++++---------- core/image_io/tiff.cpp | 19 +++++++++---------- core/image_io/tiff.h | 19 +++++++++---------- 5 files changed, 46 insertions(+), 50 deletions(-) diff --git a/core/file/tiff.cpp b/core/file/tiff.cpp index 3dead34950..e0774271cb 100644 --- a/core/file/tiff.cpp +++ b/core/file/tiff.cpp @@ -1,18 +1,17 @@ -/* - * Copyright (c) 2008-2016 the MRtrix3 contributors - * +/* Copyright (c) 2008-2017 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/ - * + * 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 - * + * but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see http://www.mrtrix.org/. */ + #ifdef MRTRIX_TIFF_SUPPORT #include "file/tiff.h" diff --git a/core/file/tiff.h b/core/file/tiff.h index 7e25c9f309..e95bdca821 100644 --- a/core/file/tiff.h +++ b/core/file/tiff.h @@ -1,18 +1,17 @@ -/* - * Copyright (c) 2008-2016 the MRtrix3 contributors - * +/* Copyright (c) 2008-2017 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/ - * + * 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 - * + * but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see http://www.mrtrix.org/. */ + #ifndef __file_tiff_h__ #define __file_tiff_h__ diff --git a/core/formats/tiff.cpp b/core/formats/tiff.cpp index 6ad3240735..842bb23eb8 100644 --- a/core/formats/tiff.cpp +++ b/core/formats/tiff.cpp @@ -1,17 +1,17 @@ -/* - * Copyright (c) 2008-2016 the MRtrix3 contributors - * +/* Copyright (c) 2008-2017 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/ - * + * 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 - * + * but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see http://www.mrtrix.org/. */ + + #ifdef MRTRIX_TIFF_SUPPORT #include "file/config.h" diff --git a/core/image_io/tiff.cpp b/core/image_io/tiff.cpp index 1ae9b39ad3..8fbd6808a6 100644 --- a/core/image_io/tiff.cpp +++ b/core/image_io/tiff.cpp @@ -1,18 +1,17 @@ -/* - * Copyright (c) 2008-2016 the MRtrix3 contributors - * +/* Copyright (c) 2008-2017 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/ - * + * 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 - * + * but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see http://www.mrtrix.org/. */ + #ifdef MRTRIX_TIFF_SUPPORT #include "header.h" diff --git a/core/image_io/tiff.h b/core/image_io/tiff.h index d455f07160..ae68aa771b 100644 --- a/core/image_io/tiff.h +++ b/core/image_io/tiff.h @@ -1,18 +1,17 @@ -/* - * Copyright (c) 2008-2016 the MRtrix3 contributors - * +/* Copyright (c) 2008-2017 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/ - * + * 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 - * + * but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * For more details, see http://www.mrtrix.org/. */ + #ifndef __image_handler_tiff_h__ #define __image_handler_tiff_h__ From f96cc5fbb1c305cbbd14adb2fc57156465691d94 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 21 Jul 2017 16:53:13 +1000 Subject: [PATCH 108/139] mrtransform & mrresize: Fix nearest-neighbour downsampling When using nearest-neighbour interpolation and mapping to a lower-resolution image, if the default oversampling strategy is used, this will result in averaging of sub-voxel samples and therefore output intensities that do include some degree of "interpolation" with respect to the input image. This change disables oversampling when using nearest-neighbour interpolation. --- cmd/mrtransform.cpp | 2 +- core/filter/resize.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/mrtransform.cpp b/cmd/mrtransform.cpp index 4133cf3448..41bb4eeb96 100644 --- a/cmd/mrtransform.cpp +++ b/cmd/mrtransform.cpp @@ -478,7 +478,7 @@ void run () switch (interp) { case 0: - Filter::reslice (input, output, linear_transform, Adapter::AutoOverSample, out_of_bounds_value); + Filter::reslice (input, output, linear_transform, { 1, 1, 1 }, out_of_bounds_value); break; case 1: Filter::reslice (input, output, linear_transform, Adapter::AutoOverSample, out_of_bounds_value); diff --git a/core/filter/resize.h b/core/filter/resize.h index 7ba663c20b..e656671f01 100644 --- a/core/filter/resize.h +++ b/core/filter/resize.h @@ -131,7 +131,8 @@ namespace MR { switch (interp_type) { case 0: - reslice (input, output); + // Prevent use of oversampling when using nearest-neighbour interpolation + reslice (input, output, Adapter::NoTransform, { 1, 1, 1 }); break; case 1: reslice (input, output); From 81990062ce7e5909425b278b2b688826cdad4b60 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Fri, 21 Jul 2017 11:15:18 +0100 Subject: [PATCH 109/139] mrtransform: add -oversample commmand to over-ride default behaviour as discussed on #1062 --- cmd/mrtransform.cpp | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/cmd/mrtransform.cpp b/cmd/mrtransform.cpp index 41bb4eeb96..8887712a6d 100644 --- a/cmd/mrtransform.cpp +++ b/cmd/mrtransform.cpp @@ -123,6 +123,14 @@ void usage () "set the interpolation method to use when reslicing (choices: nearest, linear, cubic, sinc. Default: cubic).") + Argument ("method").type_choice (interp_choices) + + Option ("oversample", + "set the amount of over-sampling (in the target space) to perform when regridding. This is particularly " + "relevant when downsamping a high-resolution image to a low-resolution image, to avoid aliasing artefacts. " + "This can consist of a single integer, or a comma-separated list of 3 integers if different oversampling " + "factors are desired along the different axes. Default is determined from ratio of voxel dimensions (disabled " + "for nearest-neighbour interpolation).") + + Argument ("factor").type_sequence_int() + + OptionGroup ("Non-linear transformation options") // TODO point users to a documentation page describing the warp field format @@ -441,6 +449,25 @@ void run () WARN ("interpolator choice ignored since the input image will not be regridded"); } + // over-sampling + vector oversample = Adapter::AutoOverSample; + opt = get_options ("oversample"); + if (opt.size()) { + oversample = opt[0][0]; + if (oversample.size() == 1) + oversample.resize (3, oversample[0]); + else if (oversample.size() != 3) + throw Exception ("-oversample option requires either a single integer, or a comma-separated list of 3 integers"); + for (const auto x : oversample) + if (x < 1) + throw Exception ("-oversample factors must be positive integers"); + } + else if (interp == 0) + // default for nearest-neighbour is no oversampling + oversample = { 1, 1, 1 }; + + + // Out of bounds value float out_of_bounds_value = 0.0; opt = get_options ("nan"); @@ -478,16 +505,16 @@ void run () switch (interp) { case 0: - Filter::reslice (input, output, linear_transform, { 1, 1, 1 }, out_of_bounds_value); + Filter::reslice (input, output, linear_transform, oversample, out_of_bounds_value); break; case 1: - Filter::reslice (input, output, linear_transform, Adapter::AutoOverSample, out_of_bounds_value); + Filter::reslice (input, output, linear_transform, oversample, out_of_bounds_value); break; case 2: - Filter::reslice (input, output, linear_transform, Adapter::AutoOverSample, out_of_bounds_value); + Filter::reslice (input, output, linear_transform, oversample, out_of_bounds_value); break; case 3: - Filter::reslice (input, output, linear_transform, Adapter::AutoOverSample, out_of_bounds_value); + Filter::reslice (input, output, linear_transform, oversample, out_of_bounds_value); break; default: assert (0); From 8cc9220db7bbf0022b3316c8af9c33e9f9faa32d Mon Sep 17 00:00:00 2001 From: Max Pietsch Date: Fri, 21 Jul 2017 13:02:20 +0100 Subject: [PATCH 110/139] mrtransform: oversample regridding of warp, warning, added oversample argument to warp filter #1067 --- cmd/mrtransform.cpp | 23 +++++++++++++---------- core/filter/warp.h | 6 ++++-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/cmd/mrtransform.cpp b/cmd/mrtransform.cpp index 8887712a6d..9f9bec01e7 100644 --- a/cmd/mrtransform.cpp +++ b/cmd/mrtransform.cpp @@ -178,19 +178,20 @@ void usage () "Use NaN as the out of bounds value (Default: 0.0)"); } -void apply_warp (Image& input, Image& output, Image& warp, const int interp, const float out_of_bounds_value) { +void apply_warp (Image& input, Image& output, Image& warp, + const int interp, const float out_of_bounds_value, const vector& oversample) { switch (interp) { case 0: - Filter::warp (input, output, warp, out_of_bounds_value); + Filter::warp (input, output, warp, out_of_bounds_value, oversample); break; case 1: - Filter::warp (input, output, warp, out_of_bounds_value); + Filter::warp (input, output, warp, out_of_bounds_value, oversample); break; case 2: - Filter::warp (input, output, warp, out_of_bounds_value); + Filter::warp (input, output, warp, out_of_bounds_value, oversample); break; case 3: - Filter::warp (input, output, warp, out_of_bounds_value); + Filter::warp (input, output, warp, out_of_bounds_value, oversample); break; default: assert (0); @@ -453,13 +454,15 @@ void run () vector oversample = Adapter::AutoOverSample; opt = get_options ("oversample"); if (opt.size()) { + if (!template_header.valid() && !warp) + throw Exception ("-oversample option applies only to regridding using the template option or to non-linear transformations"); oversample = opt[0][0]; - if (oversample.size() == 1) + if (oversample.size() == 1) oversample.resize (3, oversample[0]); else if (oversample.size() != 3) throw Exception ("-oversample option requires either a single integer, or a comma-separated list of 3 integers"); for (const auto x : oversample) - if (x < 1) + if (x < 1) throw Exception ("-oversample factors must be positive integers"); } else if (interp == 0) @@ -550,7 +553,7 @@ void run () } else { warp_deform = Registration::Warp::compute_full_deformation (warp, template_header, from); } - apply_warp (input, output, warp_deform, interp, out_of_bounds_value); + apply_warp (input, output, warp_deform, interp, out_of_bounds_value, oversample); if (fod_reorientation) Registration::Transform::reorient_warp ("reorienting", output, warp_deform, directions_cartesian.transpose(), modulate); @@ -558,13 +561,13 @@ void run () } else if (warp.ndim() == 4 && linear) { auto warp_composed = Image::scratch (warp); Registration::Warp::compose_linear_deformation (linear_transform, warp, warp_composed); - apply_warp (input, output, warp_composed, interp, out_of_bounds_value); + apply_warp (input, output, warp_composed, interp, out_of_bounds_value, oversample); if (fod_reorientation) Registration::Transform::reorient_warp ("reorienting", output, warp_composed, directions_cartesian.transpose(), modulate); // Apply 4D deformation field only } else { - apply_warp (input, output, warp, interp, out_of_bounds_value); + apply_warp (input, output, warp, interp, out_of_bounds_value, oversample); if (fod_reorientation) Registration::Transform::reorient_warp ("reorienting", output, warp, directions_cartesian.transpose(), modulate); } diff --git a/core/filter/warp.h b/core/filter/warp.h index 0a9f5e651d..cc3b47f5db 100644 --- a/core/filter/warp.h +++ b/core/filter/warp.h @@ -63,7 +63,8 @@ namespace MR ImageTypeSource& source, ImageTypeDestination& destination, WarpType& warp, - const typename ImageTypeDestination::value_type value_when_out_of_bounds = Interpolator::default_out_of_bounds_value()) + const typename ImageTypeDestination::value_type value_when_out_of_bounds = Interpolator::default_out_of_bounds_value(), + vector oversample = Adapter::AutoOverSample) { // reslice warp onto destination grid @@ -76,7 +77,8 @@ namespace MR header.size(3) = 3; Stride::set (header, Stride::contiguous_along_axis (3)); auto warp_resliced = Image::scratch (header); - reslice (warp, warp_resliced); + transform_type identity_transform; + reslice (warp, warp_resliced, identity_transform, oversample); Adapter::Warp > interp (source, warp_resliced, value_when_out_of_bounds); if (destination.ndim() == 4) From 342542db3eb0e98c7709bac403ccba29f3a0c4d1 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Fri, 21 Jul 2017 17:17:17 +0100 Subject: [PATCH 111/139] mrdegibbs: clean up incoming code - add copyright notice, author info, requisite usage() fields - use Math::pi - some style changes --- cmd/mrdegibbs.cpp | 316 ++++++++++++++++++++++++++++++++++++++++++++++ cmd/mrgrc.cpp | 301 ------------------------------------------- 2 files changed, 316 insertions(+), 301 deletions(-) create mode 100644 cmd/mrdegibbs.cpp delete mode 100644 cmd/mrgrc.cpp diff --git a/cmd/mrdegibbs.cpp b/cmd/mrdegibbs.cpp new file mode 100644 index 0000000000..52d452ea91 --- /dev/null +++ b/cmd/mrdegibbs.cpp @@ -0,0 +1,316 @@ +/* Copyright (c) 2008-2017 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 http://www.mrtrix.org/. + */ + + +#include "command.h" +#include "progressbar.h" +#include "image.h" +#include "algo/threaded_loop.h" +#include +#include + +using namespace MR; +using namespace App; + +void usage () +{ + AUTHOR = "Ben Jeurissen (ben.jeurissen@uantwerpen.be)"; + + SYNOPSIS = "Remove Gibbs Ringing Artifact"; + + DESCRIPTION + + "to be filled in"; + + ARGUMENTS + + Argument ("in", "the input image.").type_image_in () + + Argument ("out", "the output image.").type_image_out (); + + + OPTIONS + + Option ("axes", + "select the slice axes (default: 0,1 - i.e. x-y)") + + Argument ("list").type_sequence_int (); + + + REFERENCES + + "Kellner ref"; +} + + +typedef double value_type; + + +void unring_1d (fftw_complex* data, size_t n, size_t numlines, size_t nsh, size_t minW, size_t maxW) +{ + fftw_complex* in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n); + fftw_complex* out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n); + fftw_plan p = fftw_plan_dft_1d(n, in, out, FFTW_FORWARD , FFTW_ESTIMATE); + fftw_plan pinv = fftw_plan_dft_1d(n, in, out, FFTW_BACKWARD, FFTW_ESTIMATE); + fftw_complex* sh = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n*(2*nsh+1)); + fftw_complex* sh2 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n*(2*nsh+1)); + + double nfac = 1/double(n); + size_t* shifts = (size_t*) malloc(sizeof(size_t)*(2*nsh+1)); + shifts[0] = 0; + for (size_t j = 0; j < nsh; j++) { + shifts[j+1] = j+1; + shifts[1+nsh+j] = -(j+1); + } + + double TV1arr[2*nsh+1]; + double TV2arr[2*nsh+1]; + + for (size_t k = 0; k < numlines; k++) { + fftw_execute_dft(p, &(data[n*k]), sh); + size_t maxn = (n%2==1) ? (n-1)/2 : n/2-1; + + for (size_t j = 1; j < 2*nsh+1; j++) { + double phi = Math::pi/double(n) * double(shifts[j])/double(nsh); + fftw_complex u = {cos(phi),sin(phi)}; + fftw_complex e = {1,0}; + sh[j*n ][0] = sh[0][0]; + sh[j*n ][1] = sh[0][1]; + + if (n%2 == 0) { + sh[j*n + n/2][0] = 0; + sh[j*n + n/2][1] = 0; + } + + for (size_t l = 0; l < maxn; l++) { + double tmp = e[0]; + e[0] = u[0]*e[0] - u[1]*e[1]; + e[1] = tmp*u[1] + u[0]*e[1]; + + size_t L = l+1; + sh[j*n +L][0] = (e[0]*sh[L][0] - e[1]*sh[L][1]); + sh[j*n +L][1] = (e[0]*sh[L][1] + e[1]*sh[L][0]); + L = n-1-l; + sh[j*n +L][0] = (e[0]*sh[L][0] + e[1]*sh[L][1]); + sh[j*n +L][1] = (e[0]*sh[L][1] - e[1]*sh[L][0]); + + } + } + + + for (size_t j = 0; j < 2*nsh+1; j++) + fftw_execute_dft(pinv, &(sh[j*n]), &sh2[j*n]); + + for (size_t j=0; j < 2*nsh+1; j++) { + TV1arr[j] = 0; + TV2arr[j] = 0; + const size_t l = 0; + for (size_t t = minW; t <= maxW; t++) { + TV1arr[j] += fabs(sh2[j*n + (l-t+n)%n ][0] - sh2[j*n + (l-(t+1)+n)%n ][0]); + TV1arr[j] += fabs(sh2[j*n + (l-t+n)%n ][1] - sh2[j*n + (l-(t+1)+n)%n ][1]); + TV2arr[j] += fabs(sh2[j*n + (l+t+n)%n ][0] - sh2[j*n + (l+(t+1)+n)%n ][0]); + TV2arr[j] += fabs(sh2[j*n + (l+t+n)%n ][1] - sh2[j*n + (l+(t+1)+n)%n ][1]); + } + } + + for(size_t l=0; l < n; l++) { + double minTV = 999999999999; + size_t minidx= 0; + for (size_t j=0;j < 2*nsh+1; j++) { + + if (TV1arr[j] < minTV) { + minTV = TV1arr[j]; + minidx = j; + } + if (TV2arr[j] < minTV) { + minTV = TV2arr[j]; + minidx = j; + } + + TV1arr[j] += fabs(sh2[j*n + (l-minW+1+n)%n ][0] - sh2[j*n + (l-(minW)+n)%n ][0]); + TV1arr[j] -= fabs(sh2[j*n + (l-maxW+n)%n ][0] - sh2[j*n + (l-(maxW+1)+n)%n ][0]); + TV2arr[j] += fabs(sh2[j*n + (l+maxW+1+n)%n ][0] - sh2[j*n + (l+(maxW+2)+n)%n ][0]); + TV2arr[j] -= fabs(sh2[j*n + (l+minW+n)%n ][0] - sh2[j*n + (l+(minW+1)+n)%n ][0]); + + TV1arr[j] += fabs(sh2[j*n + (l-minW+1+n)%n ][1] - sh2[j*n + (l-(minW)+n)%n ][1]); + TV1arr[j] -= fabs(sh2[j*n + (l-maxW+n)%n ][1] - sh2[j*n + (l-(maxW+1)+n)%n ][1]); + TV2arr[j] += fabs(sh2[j*n + (l+maxW+1+n)%n ][1] - sh2[j*n + (l+(maxW+2)+n)%n ][1]); + TV2arr[j] -= fabs(sh2[j*n + (l+minW+n)%n ][1] - sh2[j*n + (l+(minW+1)+n)%n ][1]); + } + + double a0r = sh2[minidx*n + (l-1+n)%n ][0]; + double a1r = sh2[minidx*n + l][0]; + double a2r = sh2[minidx*n + (l+1+n)%n ][0]; + double a0i = sh2[minidx*n + (l-1+n)%n ][1]; + double a1i = sh2[minidx*n + l][1]; + double a2i = sh2[minidx*n + (l+1+n)%n ][1]; + double s = double(shifts[minidx])/nsh/2; + + if (s>0) { + data[k*n + l][0] = (a1r*(1-s) + a0r*s)*nfac; + data[k*n + l][1] = (a1i*(1-s) + a0i*s)*nfac; + } + else { + s = -s; + data[k*n + l][0] = (a1r*(1-s) + a2r*s)*nfac; + data[k*n + l][1] = (a1i*(1-s) + a2i*s)*nfac; + } + } + } + free(shifts); + fftw_destroy_plan(p); + fftw_destroy_plan(pinv); + fftw_free(in); + fftw_free(out); + fftw_free(sh); + fftw_free(sh2); +} + + + + + + + +void unring_2d (fftw_complex* data1,fftw_complex* tmp2, const size_t* dim_sz, size_t nsh, size_t minW, size_t maxW) +{ + fftw_complex* tmp1 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * dim_sz[0]*dim_sz[1]); + fftw_complex* data2 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * dim_sz[0]*dim_sz[1]); + + fftw_plan p = fftw_plan_dft_2d(dim_sz[1],dim_sz[0], data1, tmp1, FFTW_FORWARD, FFTW_ESTIMATE); + fftw_plan pinv = fftw_plan_dft_2d(dim_sz[1],dim_sz[0], data1, tmp1, FFTW_BACKWARD, FFTW_ESTIMATE); + fftw_plan p_tr = fftw_plan_dft_2d(dim_sz[0],dim_sz[1], data2, tmp2, FFTW_FORWARD, FFTW_ESTIMATE); + fftw_plan pinv_tr = fftw_plan_dft_2d(dim_sz[0],dim_sz[1], data2, tmp2, FFTW_BACKWARD, FFTW_ESTIMATE); + double nfac = 1/double(dim_sz[0]*dim_sz[1]); + + for (size_t k = 0; k < dim_sz[1]; k++) { + for (size_t j = 0; j < dim_sz[0]; j++) { + data2[j*dim_sz[1]+k][0] = data1[k*dim_sz[0]+j][0]; + data2[j*dim_sz[1]+k][1] = data1[k*dim_sz[0]+j][1]; + } + } + + fftw_execute_dft(p,data1,tmp1); + fftw_execute_dft(p_tr,data2,tmp2); + + for (size_t k = 0; k < dim_sz[1]; k++) { + double ck = (1+cos(2*Math::pi*(double(k)/dim_sz[1])))*0.5; + for (size_t j = 0 ; j < dim_sz[0]; j++) { + double cj = (1+cos(2.0*Math::pi*(double(j)/dim_sz[0])))*0.5; + tmp1[k*dim_sz[0]+j][0] = nfac*(tmp1[k*dim_sz[0]+j][0] * ck) / (ck+cj); + tmp1[k*dim_sz[0]+j][1] = nfac*(tmp1[k*dim_sz[0]+j][1] * ck) / (ck+cj); + tmp2[j*dim_sz[1]+k][0] = nfac*(tmp2[j*dim_sz[1]+k][0] * cj) / (ck+cj); + tmp2[j*dim_sz[1]+k][1] = nfac*(tmp2[j*dim_sz[1]+k][1] * cj) / (ck+cj); + } + } + + fftw_execute_dft(pinv,tmp1,data1); + fftw_execute_dft(pinv_tr,tmp2,data2); + + unring_1d(data1,dim_sz[0],dim_sz[1],nsh,minW,maxW); + unring_1d(data2,dim_sz[1],dim_sz[0],nsh,minW,maxW); + + fftw_execute_dft(p,data1,tmp1); + fftw_execute_dft(p_tr,data2,tmp2); + + for (size_t k = 0; k < dim_sz[1]; k++) { + for (size_t j = 0; j < dim_sz[0]; j++) { + tmp1[k*dim_sz[0]+j][0] = nfac*(tmp1[k*dim_sz[0]+j][0] + tmp2[j*dim_sz[1]+k][0]); + tmp1[k*dim_sz[0]+j][1] = nfac*(tmp1[k*dim_sz[0]+j][1] + tmp2[j*dim_sz[1]+k][1]); + } + } + + fftw_execute_dft(pinv,tmp1,tmp2); + + fftw_free(data2); + fftw_free(tmp1); +} + + + + + +class ComputeSlice +{ + public: + ComputeSlice (const std::vector& outer_axes, const std::vector& slice_axes, const size_t& nsh, const size_t& minW, const size_t& maxW, Image& in, Image& out) : + outer_axes (outer_axes), + slice_axes (slice_axes), + nsh (nsh), + minW (minW), + maxW (maxW), + in (in), + out (out) { } + + void operator() (const Iterator& pos) + { + assign_pos_of (pos, outer_axes).to (in, out); + size_t dims[2]; + dims[0] = in.size(slice_axes[0]); + dims[1] = in.size(slice_axes[1]); + fftw_complex* in_complex = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*dims[0]*dims[1]); + size_t i = 0; + for (auto l = Loop (slice_axes) (in); l; ++l) { + in_complex[i][0] = in.value(); + in_complex[i++][1] = 0; + } + fftw_complex* out_complex = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*dims[0]*dims[1]); + unring_2d(in_complex,out_complex,dims,nsh,minW,maxW); + fftw_free(in_complex); + i = 0; + for (auto l = Loop (slice_axes) (out); l; ++l) + out.value() = out_complex[i++][0]; + + fftw_free(out_complex); + } + + private: + const std::vector& outer_axes; + const std::vector& slice_axes; + const size_t nsh; + const size_t minW; + const size_t maxW; + Image in, out; +}; + + + + + + + +void run () +{ + auto in = Image::open (argument[0]); + auto header = in.header(); + + header.datatype() = DataType::Float64; + auto out = Image::create (argument[1], header); + + std::vector slice_axes = { 0, 1 }; + auto opt = get_options ("axes"); + if (opt.size()) { + std::vector axes = opt[0][0]; + if (slice_axes.size() != 2) + throw Exception ("slice axes must be specified as a comma-separated 2-vector"); + slice_axes = { size_t(axes[0]), size_t(axes[1]) }; + } + + // build vector of outer axes: + std::vector outer_axes (header.ndim()); + std::iota (outer_axes.begin(), outer_axes.end(), 0); + for (const auto axis : slice_axes) { + auto it = std::find (outer_axes.begin(), outer_axes.end(), axis); + if (it == outer_axes.end()) + throw Exception ("slice axis out of range!"); + outer_axes.erase (it); + } + + ThreadedLoop ("computing stuff...", in, outer_axes, slice_axes) + .run_outer (ComputeSlice (outer_axes, slice_axes, 30, 1, 3, in, out)); +} + diff --git a/cmd/mrgrc.cpp b/cmd/mrgrc.cpp deleted file mode 100644 index c507c5f483..0000000000 --- a/cmd/mrgrc.cpp +++ /dev/null @@ -1,301 +0,0 @@ -#include "command.h" -#include "progressbar.h" -#include "image.h" -#include "algo/threaded_loop.h" -#include -#include - -using namespace MR; -using namespace App; - -void usage () -{ - DESCRIPTION - + "Remove Gibbs Ringing Artifact"; - - ARGUMENTS - + Argument ("in", "the input image.").type_image_in () - + Argument ("out", "the output image.").type_image_out (); - - - OPTIONS - + Option ("axes", - "select the slice axes (default: 0,1 - i.e. x-y)") - + Argument ("list").type_sequence_int (); -} - - -constexpr double pi = 3.1416; -typedef double value_type; - - -void unring_1d(fftw_complex* data, size_t n, size_t numlines, size_t nsh, size_t minW, size_t maxW) { - fftw_complex* in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n); - fftw_complex* out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n); - fftw_plan p = fftw_plan_dft_1d(n, in, out, FFTW_FORWARD , FFTW_ESTIMATE); - fftw_plan pinv = fftw_plan_dft_1d(n, in, out, FFTW_BACKWARD, FFTW_ESTIMATE); - fftw_complex* sh = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n*(2*nsh+1)); - fftw_complex* sh2 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n*(2*nsh+1)); - double nfac = 1/double(n); - size_t* shifts = (size_t*) malloc(sizeof(size_t)*(2*nsh+1)); - shifts[0] = 0; - for (size_t j = 0; j < nsh; j++) - { - shifts[j+1] = j+1; - shifts[1+nsh+j] = -(j+1); - } - - double TV1arr[2*nsh+1]; - double TV2arr[2*nsh+1]; - - for (size_t k = 0; k < numlines; k++) - { - fftw_execute_dft(p, &(data[n*k]), sh); - size_t maxn = (n%2==1) ? (n-1)/2 : n/2-1; - - for (size_t j = 1; j < 2*nsh+1; j++) - { - double phi = pi/double(n) * double(shifts[j])/double(nsh); - fftw_complex u = {cos(phi),sin(phi)}; - fftw_complex e = {1,0}; - sh[j*n ][0] = sh[0][0]; - sh[j*n ][1] = sh[0][1]; - - if (n%2 == 0) - { - sh[j*n + n/2][0] = 0; - sh[j*n + n/2][1] = 0; - } - - for (size_t l = 0; l < maxn; l++) - { - double tmp = e[0]; - e[0] = u[0]*e[0] - u[1]*e[1]; - e[1] = tmp*u[1] + u[0]*e[1]; - - size_t L = l+1; - sh[j*n +L][0] = (e[0]*sh[L][0] - e[1]*sh[L][1]); - sh[j*n +L][1] = (e[0]*sh[L][1] + e[1]*sh[L][0]); - L = n-1-l; - sh[j*n +L][0] = (e[0]*sh[L][0] + e[1]*sh[L][1]); - sh[j*n +L][1] = (e[0]*sh[L][1] - e[1]*sh[L][0]); - - } - } - - - for (size_t j = 0; j < 2*nsh+1; j++) - { - fftw_execute_dft(pinv, &(sh[j*n]), &sh2[j*n]); - } - - for (size_t j=0; j < 2*nsh+1; j++) - { - TV1arr[j] = 0; - TV2arr[j] = 0; - const size_t l = 0; - for (size_t t = minW; t <= maxW; t++) - { - TV1arr[j] += fabs(sh2[j*n + (l-t+n)%n ][0] - sh2[j*n + (l-(t+1)+n)%n ][0]); - TV1arr[j] += fabs(sh2[j*n + (l-t+n)%n ][1] - sh2[j*n + (l-(t+1)+n)%n ][1]); - TV2arr[j] += fabs(sh2[j*n + (l+t+n)%n ][0] - sh2[j*n + (l+(t+1)+n)%n ][0]); - TV2arr[j] += fabs(sh2[j*n + (l+t+n)%n ][1] - sh2[j*n + (l+(t+1)+n)%n ][1]); - } - } - - for(size_t l=0; l < n; l++) - { - double minTV = 999999999999; - size_t minidx= 0; - for (size_t j=0;j < 2*nsh+1; j++) - { - - if (TV1arr[j] < minTV) - { - minTV = TV1arr[j]; - minidx = j; - } - if (TV2arr[j] < minTV) - { - minTV = TV2arr[j]; - minidx = j; - } - - TV1arr[j] += fabs(sh2[j*n + (l-minW+1+n)%n ][0] - sh2[j*n + (l-(minW)+n)%n ][0]); - TV1arr[j] -= fabs(sh2[j*n + (l-maxW+n)%n ][0] - sh2[j*n + (l-(maxW+1)+n)%n ][0]); - TV2arr[j] += fabs(sh2[j*n + (l+maxW+1+n)%n ][0] - sh2[j*n + (l+(maxW+2)+n)%n ][0]); - TV2arr[j] -= fabs(sh2[j*n + (l+minW+n)%n ][0] - sh2[j*n + (l+(minW+1)+n)%n ][0]); - - TV1arr[j] += fabs(sh2[j*n + (l-minW+1+n)%n ][1] - sh2[j*n + (l-(minW)+n)%n ][1]); - TV1arr[j] -= fabs(sh2[j*n + (l-maxW+n)%n ][1] - sh2[j*n + (l-(maxW+1)+n)%n ][1]); - TV2arr[j] += fabs(sh2[j*n + (l+maxW+1+n)%n ][1] - sh2[j*n + (l+(maxW+2)+n)%n ][1]); - TV2arr[j] -= fabs(sh2[j*n + (l+minW+n)%n ][1] - sh2[j*n + (l+(minW+1)+n)%n ][1]); - } - - double a0r = sh2[minidx*n + (l-1+n)%n ][0]; - double a1r = sh2[minidx*n + l][0]; - double a2r = sh2[minidx*n + (l+1+n)%n ][0]; - double a0i = sh2[minidx*n + (l-1+n)%n ][1]; - double a1i = sh2[minidx*n + l][1]; - double a2i = sh2[minidx*n + (l+1+n)%n ][1]; - double s = double(shifts[minidx])/nsh/2; - - if (s>0) - { - data[k*n + l][0] = (a1r*(1-s) + a0r*s)*nfac; - data[k*n + l][1] = (a1i*(1-s) + a0i*s)*nfac; - } - else - { - s = -s; - data[k*n + l][0] = (a1r*(1-s) + a2r*s)*nfac; - data[k*n + l][1] = (a1i*(1-s) + a2i*s)*nfac; - } - } - } - free(shifts); - fftw_destroy_plan(p); - fftw_destroy_plan(pinv); - fftw_free(in); - fftw_free(out); - fftw_free(sh); - fftw_free(sh2); -} - - -void unring_2d(fftw_complex* data1,fftw_complex* tmp2, const size_t* dim_sz, size_t nsh, size_t minW, size_t maxW) { - fftw_complex* tmp1 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * dim_sz[0]*dim_sz[1]); - fftw_complex* data2 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * dim_sz[0]*dim_sz[1]); - - fftw_plan p = fftw_plan_dft_2d(dim_sz[1],dim_sz[0], data1, tmp1, FFTW_FORWARD, FFTW_ESTIMATE); - fftw_plan pinv = fftw_plan_dft_2d(dim_sz[1],dim_sz[0], data1, tmp1, FFTW_BACKWARD, FFTW_ESTIMATE); - fftw_plan p_tr = fftw_plan_dft_2d(dim_sz[0],dim_sz[1], data2, tmp2, FFTW_FORWARD, FFTW_ESTIMATE); - fftw_plan pinv_tr = fftw_plan_dft_2d(dim_sz[0],dim_sz[1], data2, tmp2, FFTW_BACKWARD, FFTW_ESTIMATE); - double nfac = 1/double(dim_sz[0]*dim_sz[1]); - - for (size_t k = 0; k < dim_sz[1]; k++) - { - for (size_t j = 0; j < dim_sz[0]; j++) - { - data2[j*dim_sz[1]+k][0] = data1[k*dim_sz[0]+j][0]; - data2[j*dim_sz[1]+k][1] = data1[k*dim_sz[0]+j][1]; - } - } - - fftw_execute_dft(p,data1,tmp1); - fftw_execute_dft(p_tr,data2,tmp2); - - for (size_t k = 0; k < dim_sz[1]; k++) - { - double ck = (1+cos(2*pi*(double(k)/dim_sz[1])))*0.5; - for (size_t j = 0 ; j < dim_sz[0]; j++) - { - double cj = (1+cos(2*pi*(double(j)/dim_sz[0])))*0.5; - tmp1[k*dim_sz[0]+j][0] = nfac*(tmp1[k*dim_sz[0]+j][0] * ck) / (ck+cj); - tmp1[k*dim_sz[0]+j][1] = nfac*(tmp1[k*dim_sz[0]+j][1] * ck) / (ck+cj); - tmp2[j*dim_sz[1]+k][0] = nfac*(tmp2[j*dim_sz[1]+k][0] * cj) / (ck+cj); - tmp2[j*dim_sz[1]+k][1] = nfac*(tmp2[j*dim_sz[1]+k][1] * cj) / (ck+cj); - } - } - - fftw_execute_dft(pinv,tmp1,data1); - fftw_execute_dft(pinv_tr,tmp2,data2); - - unring_1d(data1,dim_sz[0],dim_sz[1],nsh,minW,maxW); - unring_1d(data2,dim_sz[1],dim_sz[0],nsh,minW,maxW); - - fftw_execute_dft(p,data1,tmp1); - fftw_execute_dft(p_tr,data2,tmp2); - - for (size_t k = 0; k < dim_sz[1]; k++) - { - for (size_t j = 0; j < dim_sz[0]; j++) - { - tmp1[k*dim_sz[0]+j][0] = nfac*(tmp1[k*dim_sz[0]+j][0] + tmp2[j*dim_sz[1]+k][0]); - tmp1[k*dim_sz[0]+j][1] = nfac*(tmp1[k*dim_sz[0]+j][1] + tmp2[j*dim_sz[1]+k][1]); - } - } - - fftw_execute_dft(pinv,tmp1,tmp2); - - fftw_free(data2); - fftw_free(tmp1); -} - -class ComputeSlice -{ - public: - ComputeSlice (const std::vector& outer_axes, const std::vector& slice_axes, const size_t& nsh, const size_t& minW, const size_t& maxW, Image& in, Image& out) : - outer_axes (outer_axes), - slice_axes (slice_axes), - nsh (nsh), - minW (minW), - maxW (maxW), - in (in), - out (out) { } - - void operator() (const Iterator& pos) - { - assign_pos_of (pos, outer_axes).to (in, out); - size_t dims[2]; - dims[0] = in.size(slice_axes[0]); - dims[1] = in.size(slice_axes[1]); - fftw_complex* in_complex = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*dims[0]*dims[1]); - size_t i = 0; - for (auto l = Loop (slice_axes) (in); l; ++l) - { - in_complex[i][0] = in.value(); - in_complex[i++][1] = 0; - } - fftw_complex* out_complex = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*dims[0]*dims[1]); - unring_2d(in_complex,out_complex,dims,nsh,minW,maxW); - fftw_free(in_complex); - i = 0; - for (auto l = Loop (slice_axes) (out); l; ++l) - { - out.value() = out_complex[i++][0]; - } - fftw_free(out_complex); - } - - private: - const std::vector& outer_axes; - const std::vector& slice_axes; - const size_t& nsh; - const size_t& minW; - const size_t& maxW; - Image in, out; -}; - -void run () -{ - auto in = Image::open (argument[0]); - auto header = in.header(); - - header.datatype() = DataType::Float64; - auto out = Image::create (argument[1], header); - - std::vector slice_axes = { 0, 1 }; - auto opt = get_options ("axes"); - if (opt.size()) { - std::vector axes = opt[0][0]; - if (slice_axes.size() != 2) - throw Exception ("slice axes must be specified as a comma-separated 2-vector"); - slice_axes = { size_t(axes[0]), size_t(axes[1]) }; - } - - // build vector of outer axes: - std::vector outer_axes (header.ndim()); - std::iota (outer_axes.begin(), outer_axes.end(), 0); - for (const auto axis : slice_axes) - { - auto it = std::find (outer_axes.begin(), outer_axes.end(), axis); - if (it == outer_axes.end()) - throw Exception ("slice axis out of range!"); - outer_axes.erase (it); - } - - ThreadedLoop ("computing stuff...", in, outer_axes, slice_axes) - .run_outer (ComputeSlice (outer_axes, slice_axes, 30, 1, 3, in, out)); -} - From b8148cb7daeb5c4818c89669d1f81e52ab3903ff Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Sat, 22 Jul 2017 14:36:34 +0100 Subject: [PATCH 112/139] mrdegibbs: command compiles OK with FFTW need to add -lfftw3 to final linker command --- cmd/mrdegibbs.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/mrdegibbs.cpp b/cmd/mrdegibbs.cpp index 52d452ea91..f7bce68366 100644 --- a/cmd/mrdegibbs.cpp +++ b/cmd/mrdegibbs.cpp @@ -237,7 +237,7 @@ void unring_2d (fftw_complex* data1,fftw_complex* tmp2, const size_t* dim_sz, si class ComputeSlice { public: - ComputeSlice (const std::vector& outer_axes, const std::vector& slice_axes, const size_t& nsh, const size_t& minW, const size_t& maxW, Image& in, Image& out) : + ComputeSlice (const vector& outer_axes, const vector& slice_axes, const size_t& nsh, const size_t& minW, const size_t& maxW, Image& in, Image& out) : outer_axes (outer_axes), slice_axes (slice_axes), nsh (nsh), @@ -269,8 +269,8 @@ class ComputeSlice } private: - const std::vector& outer_axes; - const std::vector& slice_axes; + const vector& outer_axes; + const vector& slice_axes; const size_t nsh; const size_t minW; const size_t maxW; @@ -286,22 +286,22 @@ class ComputeSlice void run () { auto in = Image::open (argument[0]); - auto header = in.header(); + Header header (in); header.datatype() = DataType::Float64; auto out = Image::create (argument[1], header); - std::vector slice_axes = { 0, 1 }; + vector slice_axes = { 0, 1 }; auto opt = get_options ("axes"); if (opt.size()) { - std::vector axes = opt[0][0]; + vector axes = opt[0][0]; if (slice_axes.size() != 2) throw Exception ("slice axes must be specified as a comma-separated 2-vector"); slice_axes = { size_t(axes[0]), size_t(axes[1]) }; } // build vector of outer axes: - std::vector outer_axes (header.ndim()); + vector outer_axes (header.ndim()); std::iota (outer_axes.begin(), outer_axes.end(), 0); for (const auto axis : slice_axes) { auto it = std::find (outer_axes.begin(), outer_axes.end(), axis); From 52195288d6002e130a12063c9268ef7e129ea05a Mon Sep 17 00:00:00 2001 From: Max Pietsch Date: Mon, 24 Jul 2017 16:31:25 +0100 Subject: [PATCH 113/139] scripts: fix check3DNonunity for 4D input with 1 volume --- lib/mrtrix3/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mrtrix3/image.py b/lib/mrtrix3/image.py index 4ca0a97ad7..25934ed6c5 100644 --- a/lib/mrtrix3/image.py +++ b/lib/mrtrix3/image.py @@ -8,7 +8,7 @@ def check3DNonunity(image_path): dim = [ int(i) for i in headerField(image_path, 'size').strip().split() ] if len(dim) < 3: app.error('Image \'' + image_path + '\' does not contain 3 spatial dimensions') - if min(dim) == 1: + if min(dim[:3]) == 1: app.error('Image \'' + image_path + '\' does not contain 3D spatial information (axis with size 1)') From 27ac14a01a08950f76e62899e7bb3d3810b4bc6e Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Wed, 26 Jul 2017 17:24:29 +1000 Subject: [PATCH 114/139] mrview: lightbox: Cache current selection * Current selection persists when a user toggles back and forth between view modes * Some clamping when cycling through volumes to ensure initial volume pane is strictly non-negative --- src/gui/mrview/mode/base.cpp | 2 -- src/gui/mrview/mode/base.h | 1 - src/gui/mrview/mode/lightbox.cpp | 58 +++++++++++++++++--------------- src/gui/mrview/mode/lightbox.h | 5 ++- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/gui/mrview/mode/base.cpp b/src/gui/mrview/mode/base.cpp index 7814160c4b..2709e2d26a 100644 --- a/src/gui/mrview/mode/base.cpp +++ b/src/gui/mrview/mode/base.cpp @@ -142,13 +142,11 @@ namespace MR done_painting: update_overlays = false; - finished_paintGL (); ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; } void Base::paint (Projection&) { } - void Base::finished_paintGL () {} void Base::mouse_press_event () { } void Base::mouse_release_event () { } diff --git a/src/gui/mrview/mode/base.h b/src/gui/mrview/mode/base.h index fcc13280ff..e40c3e1d6b 100644 --- a/src/gui/mrview/mode/base.h +++ b/src/gui/mrview/mode/base.h @@ -206,7 +206,6 @@ namespace MR } void reset_view (); - virtual void finished_paintGL (); bool visible; }; diff --git a/src/gui/mrview/mode/lightbox.cpp b/src/gui/mrview/mode/lightbox.cpp index bc2323492b..6b1ddb3d6c 100644 --- a/src/gui/mrview/mode/lightbox.cpp +++ b/src/gui/mrview/mode/lightbox.cpp @@ -31,17 +31,14 @@ namespace MR float LightBox::slice_focus_increment(1.f); float LightBox::slice_focus_inc_adjust_rate(0.2f); std::string LightBox::prev_image_name; + ssize_t LightBox::current_slice_index((LightBox::n_rows*LightBox::n_cols) / 2); LightBox::LightBox () : layout_is_dirty(true), - current_slice_index((n_rows*n_cols) / 2), slices_proj_focusdelta(n_rows*n_cols, proj_focusdelta(projection, 0.f)) { Image* img = image(); - if (render_volumes()) - current_slice_index = 0; - if(!img || prev_image_name != img->header().name()) image_changed_event(); else { @@ -88,6 +85,9 @@ namespace MR void LightBox::set_show_volumes(bool show_vol) { show_volumes = show_vol; + if (show_vol) { + update_volume_indices(); + } updateGL(); } @@ -105,8 +105,7 @@ namespace MR update_volume_indices(); - size_t slice_idx = render_volumes() ? - std::min(current_slice_index, (n_rows * n_cols) - 1) : (n_rows * n_cols) / 2; + ssize_t slice_idx = std::min(current_slice_index, (n_rows * n_cols) - 1); set_current_slice_index(slice_idx); update_slices_focusdelta(); @@ -115,7 +114,7 @@ namespace MR frame_VAO.clear(); } - void LightBox::set_current_slice_index(size_t slice_index) + void LightBox::set_current_slice_index(ssize_t slice_index) { int prev_index = current_slice_index; current_slice_index = slice_index; @@ -127,7 +126,8 @@ namespace MR const Eigen::Vector3f slice_focus = move_in_out_displacement(focus_delta, slice_proj); set_focus(focus() + slice_focus); update_slices_focusdelta(); - } + } else if (volume_indices[slice_index] == -1) + current_slice_index = prev_index; } void LightBox::update_slices_focusdelta() @@ -147,10 +147,25 @@ namespace MR if (!is_4d) return; - int initial_vol = image()->image.index(3); + int n_vols = image()->image.size(3); + int current_vol = image()->image.index(3); + + int initial_vol = current_vol + (int)volume_increment *- (int)current_slice_index; + if (initial_vol < 0) + current_vol = volume_increment * current_slice_index; + + + for(int i = 0, N = volume_indices.size(); i < N; ++i) { + int vol_index = current_vol + (int)volume_increment * (i - (int)current_slice_index); + vol_index = vol_index < 0 || vol_index >= n_vols ? -1 : vol_index; + volume_indices[i] = vol_index; + } - for(int i = 0, N = volume_indices.size(); i < N; ++i) - volume_indices[i] = initial_vol + (int)volume_increment * i; + for (ssize_t i = current_slice_index; i >= 0; --i) { + current_slice_index = i; + if (volume_indices[i] >= 0) + break; + } } void LightBox::draw_plane_primitive (int axis, Displayable::Shader& shader_program, Projection& with_projection) @@ -162,17 +177,6 @@ namespace MR ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; } - void LightBox::finished_paintGL () - { - // When rendering volumes, the initial state is always considered to be - // the first volume (i.e. top-left slice) - // However, for the purposes of correctly rendering voxel information we need the - // image volume to correspond to the selected volume - // Hence, this is called once paintGL has finished to restore the initial state - if(render_volumes()) - image()->image.index(3) = volume_indices[0]; - } - void LightBox::paint(Projection&) { ASSERT_GL_MRVIEW_CONTEXT_IS_CURRENT; @@ -194,13 +198,14 @@ namespace MR } bool rend_vols = render_volumes(); - bool render_plane = true; - size_t slice_idx = 0; + ssize_t slice_idx = 0; for(size_t row = 0; row < n_rows; ++row) { for(size_t col = 0; col < n_cols; ++col, ++slice_idx) { Projection& slice_proj = slices_proj_focusdelta[slice_idx].first; + bool render_plane = true; + // Place the first slice in the top-left corner slice_proj.set_viewport(window(), x + dw * col, y + h - (dh * (row+1)), dw, dh); @@ -209,8 +214,7 @@ namespace MR setup_projection (plane(), slice_proj); if (rend_vols) { - int n_vols = image()->image.size(3); - if (volume_indices[slice_idx] >= 0 && volume_indices[slice_idx] < n_vols - 1) + if (volume_indices[slice_idx] != -1) image()->image.index(3) = volume_indices[slice_idx]; else render_plane = false; @@ -236,7 +240,7 @@ namespace MR } // Restore view state - if(rend_vols) + if(rend_vols && volume_indices[current_slice_index] >= 0) image()->image.index(3) = volume_indices[current_slice_index]; set_focus(orig_focus); projection.set_viewport(window(), x, y, w, h); diff --git a/src/gui/mrview/mode/lightbox.h b/src/gui/mrview/mode/lightbox.h index f6a65ee35c..277ec7388d 100644 --- a/src/gui/mrview/mode/lightbox.h +++ b/src/gui/mrview/mode/lightbox.h @@ -69,7 +69,6 @@ namespace MR protected: void draw_plane_primitive(int axis, Displayable::Shader& shader_program, Projection& with_projection) override; - void finished_paintGL() override; private: static size_t slice_index(size_t row, size_t col) { @@ -80,7 +79,7 @@ namespace MR void update_layout(); void update_volume_indices(); void update_slices_focusdelta(); - void set_current_slice_index(size_t slice_index); + void set_current_slice_index(ssize_t slice_index); void draw_grid(); bool render_volumes(); @@ -92,7 +91,7 @@ namespace MR static float slice_focus_inc_adjust_rate; bool layout_is_dirty; - size_t current_slice_index; + static ssize_t current_slice_index; vector volume_indices; vector slices_proj_focusdelta; From 1c70e90e9912cbe9e7c50331a7c01b5dd03e3a3a Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Thu, 27 Jul 2017 10:40:58 +1000 Subject: [PATCH 115/139] mrview: lightbox: Correctly update current slice focus * Make sure current slice focus explicitly updates when toggling out of cycling through volumes * Ensures consistency with respect to currently selected view pane * i.e. The selected view pane should remain unaltered --- src/gui/mrview/mode/lightbox.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/gui/mrview/mode/lightbox.cpp b/src/gui/mrview/mode/lightbox.cpp index 6b1ddb3d6c..3bbf01b04c 100644 --- a/src/gui/mrview/mode/lightbox.cpp +++ b/src/gui/mrview/mode/lightbox.cpp @@ -85,8 +85,13 @@ namespace MR void LightBox::set_show_volumes(bool show_vol) { show_volumes = show_vol; - if (show_vol) { + if (show_vol) update_volume_indices(); + else { + // Force focus update + ssize_t prev_index = current_slice_index; + current_slice_index = -1; + set_current_slice_index(prev_index); } updateGL(); } From fb2c177b7b8c1660b23d1f5369774fd041d40366 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Tue, 1 Aug 2017 10:43:38 +1000 Subject: [PATCH 116/139] update docs --- docs/reference/config_file_options.rst | 190 ------------------------- 1 file changed, 190 deletions(-) diff --git a/docs/reference/config_file_options.rst b/docs/reference/config_file_options.rst index 3789b48360..a0e170c8df 100644 --- a/docs/reference/config_file_options.rst +++ b/docs/reference/config_file_options.rst @@ -9,16 +9,6 @@ List of MRtrix3 configuration file options The default intensity for the ambient light in OpenGL renders. -* **AmbientIntensity** - *default: 0.6* - - The default intensity for the ambient light in OpenGL renders - -* **Analyse.LeftToRight** - *default: 0 (false)* - - A boolean value to indicate whether images in Analyse format should be assumed to be in LAS orientation (default) or RAS (when this is option is turned on). - * **AnalyseLeftToRight** *default: 0 (false)* @@ -29,31 +19,11 @@ List of MRtrix3 configuration file options Specifies whether the b-values should be scaled by the squared norm of the gradient vectors when loading a DW gradient scheme. This is commonly required to correctly interpret images acquired on scanners that nominally only allow a single b-value, as the common workaround is to scale the gradient vectors to modulate the actual b-value. -* **BValueScaling** - *default: 1 (true)* - - specifies whether the b-values should be scaled by the squared norm of the gradient vectors when loading a DW gradient scheme. This is commonly required to correctly interpret images acquired on scanners that nominally only allow a single b-value, as the common workaround is to scale the gradient vectors to modulate the actual b-value. - -* **BValueScaling** - *default: yes* - - specifies whether b-values should be scaled according the DW gradient amplitudes - see the -bvalue_scaling option for details. - * **BZeroThreshold** *default: 10.0* Specifies the b-value threshold for determining those image volumes that correspond to b=0. -* **BZeroThreshold** - *default: 10.0* - - specifies the b-value threshold for determining those image volumes that correspond to b=0 - -* **BackgroundColor** - *default: 1,1,1 (white)* - - The default colour to use for the background in OpenGL panels, notably the SH viewer. - * **BackgroundColor** *default: 1.0,1.0,1.0* @@ -199,11 +169,6 @@ List of MRtrix3 configuration file options Whether or not nodes are forced to be visible when selected. -* **DiffuseIntensity** - *default: 0.3* - - The default intensity for the diffuse light in OpenGL renders - * **DiffuseIntensity** *default: 0.5* @@ -214,36 +179,16 @@ List of MRtrix3 configuration file options A boolean value specifying whether MRtrix applications should abort as soon as any (otherwise non-fatal) warning is issued. -* **FailOnWarn** - *default: 0 (false)* - - A boolean value specifying whether MRtrix applications should abort as soon as any (otherwise non-fatal) warning is issued. - * **HelpCommand** *default: less* The command to use to display each command's help page (leave empty to send directly to the terminal). -* **HelpCommand** - *default: less* - - the command to use to display each command's help page (leave empty to send directly to the terminal). - -* **IconSize** - *default: 24* - - The size of the icons in the main MRView toolbar. - * **IconSize** *default: 30* The size of the icons in the main MRView toolbar. -* **ImageBackgroundColour** - *default: 0,0,0 (black)* - - The default image background colour - * **ImageInterpolation** *default: true* @@ -259,16 +204,6 @@ List of MRtrix3 configuration file options The starting position of the MRView toolbar. Valid values are: top, bottom, left, right. -* **InitialToolBarPosition** - *default: top* - - The starting position of the MRView toolbar. Valid values are: top, bottom, left, right. - -* **LightPosition** - *default: 1,1,3* - - The default position vector to use for the light in OpenGL renders - * **LightPosition** *default: 1.0,1.0,3.0* @@ -279,16 +214,6 @@ List of MRtrix3 configuration file options The height of the colourbar in MRView, in pixels. -* **MRViewColourBarHeight** - *default: 100* - - The height of the colourbar in MRView, in pixels. - -* **MRViewColourBarInset** - *default: 20* - - How far away from the edge of the main window to place the colourbar in MRView, in pixels. - * **MRViewColourBarInset** *default: 20* @@ -299,16 +224,6 @@ List of MRtrix3 configuration file options The position of the colourbar within the main window in MRView. Valid values are: bottomleft, bottomright, topleft, topright. -* **MRViewColourBarPosition** - *default: bottomright* - - The position of the colourbar within the main window in MRView. Valid values are: bottomleft, bottomright, topleft, topright. - -* **MRViewColourBarTextOffset** - *default: 10* - - How far away from the colourbar to place the associated text, in pixels. - * **MRViewColourBarTextOffset** *default: 10* @@ -319,11 +234,6 @@ List of MRtrix3 configuration file options The width of the colourbar in MRView, in pixels. -* **MRViewColourBarWidth** - *default: 20* - - The width of the colourbar in MRView, in pixels. - * **MRViewColourHorizontalPadding** *default: 100* @@ -334,16 +244,6 @@ List of MRtrix3 configuration file options Whether MRView tools should start docked in the main window, or floating (detached from the main window). -* **MRViewDockFloating** - *default: 0 (false)* - - Whether Tools should start docked in the main window, or floating (detached from the main window). - -* **MRViewFocusModifierKey** - *default: alt (cmd on MacOSX)* - - modifier key to select focus mode in MRView. Valid choices include shift, alt, ctrl, meta (on MacOSX: shift, alt, ctrl, cmd). - * **MRViewFocusModifierKey** *default: meta (cmd on MacOSX)* @@ -369,11 +269,6 @@ List of MRtrix3 configuration file options Modifier key to select move mode in MRView. Valid choices include shift, alt, ctrl, meta (on MacOSX: shift, alt, ctrl, cmd). -* **MRViewMoveModifierKey** - *default: shift* - - modifier key to select move mode in MRView. Valid choices include shift, alt, ctrl, meta (on MacOSX: shift, alt, ctrl, cmd). - * **MRViewOdfScale** *default: 1.0* @@ -384,11 +279,6 @@ List of MRtrix3 configuration file options Modifier key to select rotate mode in MRView. Valid choices include shift, alt, ctrl, meta (on MacOSX: shift, alt, ctrl, cmd). -* **MRViewRotateModifierKey** - *default: ctrl* - - modifier key to select rotate mode in MRView. Valid choices include shift, alt, ctrl, meta (on MacOSX: shift, alt, ctrl, cmd). - * **MRViewShowColourbar** *default: true* @@ -414,11 +304,6 @@ List of MRtrix3 configuration file options Voxel information shown in main image overlay -* **MRViewToolFontSize** - *default: 2 points less than the standard system font* - - The point size for the font to use in MRView Tools. - * **MRViewToolFontSize** *default: 2 points less than the standard system font* @@ -434,16 +319,6 @@ List of MRtrix3 configuration file options How many samples to use for multi-sample anti-aliasing (to improve display quality). -* **MSAA** - *default: 0 (false)* - - How many samples to use for multi-sample anti-aliasing (to improve display quality). - -* **NIFTI.AllowBitwise** - *default: 0 (false)* - - 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.AllowBitwise** *default: 0 (false)* @@ -464,11 +339,6 @@ List of MRtrix3 configuration file options 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: 0 (true on MacOSX, false otherwise)* - - Whether the creation of an OpenGL 3.3 context requires it to be a core profile (needed on newer versions of the ATI drivers on Linux, for instance). - * **NeedOpenGLCoreProfile** *default: 1 (true)* @@ -479,16 +349,6 @@ List of MRtrix3 configuration file options Set the default number of CPU threads to use for multi-threading. -* **NumberOfThreads** - *default: number of threads provided by hardware* - - set the default number of CPU threads to use for multi-threading. - -* **NumberOfUndos** - *default: 16* - - The number of undo operations permitted in the MRView ROI editor tool - * **NumberOfUndos** *default: 16* @@ -499,11 +359,6 @@ List of MRtrix3 configuration file options The default colour to use for objects (i.e. SH glyphs) when not colouring by direction. -* **ObjectColor** - *default: 1,1,0 (yellow)* - - The default colour to use for objects (i.e. SH glyphs) when not colouring by direction. - * **ScriptTmpDir** *default: `.`* @@ -519,26 +374,11 @@ List of MRtrix3 configuration file options Initial buffer size for data in MRtrix sparse image format file (in bytes). -* **SparseDataInitialSize** - *default: 16777216* - - initial buffer size for data in MRtrix sparse image format file (in bytes). - -* **SpecularExponent** - *default: 1* - - The default exponent for the specular light in OpenGL renders - * **SpecularExponent** *default: 5.0* The default exponent for the specular light in OpenGL renders. -* **SpecularIntensity** - *default: 0.4* - - The default intensity for the specular light in OpenGL renders - * **SpecularIntensity** *default: 0.5* @@ -554,16 +394,6 @@ List of MRtrix3 configuration file options A boolean value to indicate whether colours should be used in the terminal. -* **TerminalColor** - *default: 1 (true)* - - A boolean value to indicate whether colours should be used in the terminal. - -* **TmpFileDir** - *default: `/tmp` (on Unix), `.` (on Windows)* - - The prefix for temporary files (as used in pipelines). By default, these files get written to the current folder, which may cause performance issues when operating over distributed file systems. In this case, it may be better to specify `/tmp/` here. - * **TmpFileDir** *default: `/tmp` (on Unix), `.` (on Windows)* @@ -574,16 +404,6 @@ List of MRtrix3 configuration file options The prefix to use for the basename of temporary files. This will be used to generate a unique filename for the temporary file, by adding random characters to this prefix, followed by a suitable suffix (depending on file type). Note that this prefix can also be manipulated using the `MRTRIX_TMPFILE_PREFIX` environment variable, without editing the config file. -* **TmpFilePrefix** - *default: `mrtrix-tmp-`* - - The prefix to use for the basename of temporary files. This will be used to generate a unique filename for the temporary file, by adding random characters to this prefix, followed by a suitable suffix (depending on file type). Note that this prefix can also be manipulated using the `MRTRIX_TMPFILE_PREFIX` environment variable, without editing the config file. - -* **ToolbarStyle** - *default: 2* - - The style of the main toolbar buttons in MRView. See Qt's documentation for Qt::ToolButtonStyle. - * **ToolbarStyle** *default: 2* @@ -594,16 +414,6 @@ List of MRtrix3 configuration file options The size of the write-back buffer (in bytes) to use when writing track files. MRtrix will store the output tracks in a relatively large buffer to limit the number of write() calls, avoid associated issues such as file fragmentation. -* **TrackWriterBufferSize** - *default: 16777216* - - The size of the write-back buffer (in bytes) to use when writing track files. MRtrix will store the output tracks in a relatively large buffer to limit the number of write() calls, avoid associated issues such as file fragmentation. - -* **VSync** - *default: 0 (false)* - - Whether the screen update should synchronise with the monitor's vertical refresh (to avoid tearing artefacts). - * **VSync** *default: 0 (false)* From c519731c37e30cb89330210ab6c50052f9d6026b Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Tue, 1 Aug 2017 22:55:07 +0100 Subject: [PATCH 117/139] mrdegibbs: switch from size_t to int Otherwise the command produces bad output, presumably due to wraparound after subtraction of unsigned integers --- cmd/mrdegibbs.cpp | 86 ++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/cmd/mrdegibbs.cpp b/cmd/mrdegibbs.cpp index f7bce68366..45effaa73d 100644 --- a/cmd/mrdegibbs.cpp +++ b/cmd/mrdegibbs.cpp @@ -48,9 +48,10 @@ void usage () typedef double value_type; +constexpr double pi = 3.1416; -void unring_1d (fftw_complex* data, size_t n, size_t numlines, size_t nsh, size_t minW, size_t maxW) +void unring_1d (fftw_complex* data, int n, int numlines, int nsh, int minW, int maxW) { fftw_complex* in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n); fftw_complex* out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n); @@ -60,9 +61,9 @@ void unring_1d (fftw_complex* data, size_t n, size_t numlines, size_t nsh, size_ fftw_complex* sh2 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n*(2*nsh+1)); double nfac = 1/double(n); - size_t* shifts = (size_t*) malloc(sizeof(size_t)*(2*nsh+1)); + int* shifts = (int*) malloc(sizeof(int)*(2*nsh+1)); shifts[0] = 0; - for (size_t j = 0; j < nsh; j++) { + for (int j = 0; j < nsh; j++) { shifts[j+1] = j+1; shifts[1+nsh+j] = -(j+1); } @@ -70,12 +71,12 @@ void unring_1d (fftw_complex* data, size_t n, size_t numlines, size_t nsh, size_ double TV1arr[2*nsh+1]; double TV2arr[2*nsh+1]; - for (size_t k = 0; k < numlines; k++) { + for (int k = 0; k < numlines; k++) { fftw_execute_dft(p, &(data[n*k]), sh); - size_t maxn = (n%2==1) ? (n-1)/2 : n/2-1; + int maxn = (n%2==1) ? (n-1)/2 : n/2-1; - for (size_t j = 1; j < 2*nsh+1; j++) { - double phi = Math::pi/double(n) * double(shifts[j])/double(nsh); + for (int j = 1; j < 2*nsh+1; j++) { + double phi = pi/double(n) * double(shifts[j])/double(nsh); fftw_complex u = {cos(phi),sin(phi)}; fftw_complex e = {1,0}; sh[j*n ][0] = sh[0][0]; @@ -86,12 +87,12 @@ void unring_1d (fftw_complex* data, size_t n, size_t numlines, size_t nsh, size_ sh[j*n + n/2][1] = 0; } - for (size_t l = 0; l < maxn; l++) { + for (int l = 0; l < maxn; l++) { double tmp = e[0]; e[0] = u[0]*e[0] - u[1]*e[1]; e[1] = tmp*u[1] + u[0]*e[1]; - size_t L = l+1; + int L = l+1; sh[j*n +L][0] = (e[0]*sh[L][0] - e[1]*sh[L][1]); sh[j*n +L][1] = (e[0]*sh[L][1] + e[1]*sh[L][0]); L = n-1-l; @@ -102,14 +103,14 @@ void unring_1d (fftw_complex* data, size_t n, size_t numlines, size_t nsh, size_ } - for (size_t j = 0; j < 2*nsh+1; j++) + for (int j = 0; j < 2*nsh+1; ++j) fftw_execute_dft(pinv, &(sh[j*n]), &sh2[j*n]); - for (size_t j=0; j < 2*nsh+1; j++) { + for (int j = 0; j < 2*nsh+1; ++j) { TV1arr[j] = 0; TV2arr[j] = 0; - const size_t l = 0; - for (size_t t = minW; t <= maxW; t++) { + const int l = 0; + for (int t = minW; t <= maxW; t++) { TV1arr[j] += fabs(sh2[j*n + (l-t+n)%n ][0] - sh2[j*n + (l-(t+1)+n)%n ][0]); TV1arr[j] += fabs(sh2[j*n + (l-t+n)%n ][1] - sh2[j*n + (l-(t+1)+n)%n ][1]); TV2arr[j] += fabs(sh2[j*n + (l+t+n)%n ][0] - sh2[j*n + (l+(t+1)+n)%n ][0]); @@ -117,10 +118,10 @@ void unring_1d (fftw_complex* data, size_t n, size_t numlines, size_t nsh, size_ } } - for(size_t l=0; l < n; l++) { - double minTV = 999999999999; - size_t minidx= 0; - for (size_t j=0;j < 2*nsh+1; j++) { + for(int l=0; l < n; l++) { + double minTV = std::numeric_limits::max(); + int minidx = 0; + for (int j = 0; j < 2*nsh+1; ++j) { if (TV1arr[j] < minTV) { minTV = TV1arr[j]; @@ -176,19 +177,20 @@ void unring_1d (fftw_complex* data, size_t n, size_t numlines, size_t nsh, size_ -void unring_2d (fftw_complex* data1,fftw_complex* tmp2, const size_t* dim_sz, size_t nsh, size_t minW, size_t maxW) +void unring_2d (fftw_complex* data1, fftw_complex* tmp2, const int* dim_sz, int nsh, int minW, int maxW) { fftw_complex* tmp1 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * dim_sz[0]*dim_sz[1]); fftw_complex* data2 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * dim_sz[0]*dim_sz[1]); - fftw_plan p = fftw_plan_dft_2d(dim_sz[1],dim_sz[0], data1, tmp1, FFTW_FORWARD, FFTW_ESTIMATE); - fftw_plan pinv = fftw_plan_dft_2d(dim_sz[1],dim_sz[0], data1, tmp1, FFTW_BACKWARD, FFTW_ESTIMATE); - fftw_plan p_tr = fftw_plan_dft_2d(dim_sz[0],dim_sz[1], data2, tmp2, FFTW_FORWARD, FFTW_ESTIMATE); - fftw_plan pinv_tr = fftw_plan_dft_2d(dim_sz[0],dim_sz[1], data2, tmp2, FFTW_BACKWARD, FFTW_ESTIMATE); + fftw_plan p = fftw_plan_dft_2d (dim_sz[1],dim_sz[0], data1, tmp1, FFTW_FORWARD, FFTW_ESTIMATE); + fftw_plan pinv = fftw_plan_dft_2d (dim_sz[1],dim_sz[0], data1, tmp1, FFTW_BACKWARD, FFTW_ESTIMATE); + fftw_plan p_tr = fftw_plan_dft_2d (dim_sz[0],dim_sz[1], data2, tmp2, FFTW_FORWARD, FFTW_ESTIMATE); + fftw_plan pinv_tr = fftw_plan_dft_2d (dim_sz[0],dim_sz[1], data2, tmp2, FFTW_BACKWARD, FFTW_ESTIMATE); + double nfac = 1/double(dim_sz[0]*dim_sz[1]); - for (size_t k = 0; k < dim_sz[1]; k++) { - for (size_t j = 0; j < dim_sz[0]; j++) { + for (int k = 0; k < dim_sz[1]; k++) { + for (int j = 0; j < dim_sz[0]; j++) { data2[j*dim_sz[1]+k][0] = data1[k*dim_sz[0]+j][0]; data2[j*dim_sz[1]+k][1] = data1[k*dim_sz[0]+j][1]; } @@ -197,10 +199,10 @@ void unring_2d (fftw_complex* data1,fftw_complex* tmp2, const size_t* dim_sz, si fftw_execute_dft(p,data1,tmp1); fftw_execute_dft(p_tr,data2,tmp2); - for (size_t k = 0; k < dim_sz[1]; k++) { - double ck = (1+cos(2*Math::pi*(double(k)/dim_sz[1])))*0.5; - for (size_t j = 0 ; j < dim_sz[0]; j++) { - double cj = (1+cos(2.0*Math::pi*(double(j)/dim_sz[0])))*0.5; + for (int k = 0; k < dim_sz[1]; k++) { + double ck = (1+cos(2*pi*(double(k)/dim_sz[1])))*0.5; + for (int j = 0 ; j < dim_sz[0]; j++) { + double cj = (1+cos(2.0*pi*(double(j)/dim_sz[0])))*0.5; tmp1[k*dim_sz[0]+j][0] = nfac*(tmp1[k*dim_sz[0]+j][0] * ck) / (ck+cj); tmp1[k*dim_sz[0]+j][1] = nfac*(tmp1[k*dim_sz[0]+j][1] * ck) / (ck+cj); tmp2[j*dim_sz[1]+k][0] = nfac*(tmp2[j*dim_sz[1]+k][0] * cj) / (ck+cj); @@ -217,8 +219,8 @@ void unring_2d (fftw_complex* data1,fftw_complex* tmp2, const size_t* dim_sz, si fftw_execute_dft(p,data1,tmp1); fftw_execute_dft(p_tr,data2,tmp2); - for (size_t k = 0; k < dim_sz[1]; k++) { - for (size_t j = 0; j < dim_sz[0]; j++) { + for (int k = 0; k < dim_sz[1]; k++) { + for (int j = 0; j < dim_sz[0]; j++) { tmp1[k*dim_sz[0]+j][0] = nfac*(tmp1[k*dim_sz[0]+j][0] + tmp2[j*dim_sz[1]+k][0]); tmp1[k*dim_sz[0]+j][1] = nfac*(tmp1[k*dim_sz[0]+j][1] + tmp2[j*dim_sz[1]+k][1]); } @@ -237,7 +239,7 @@ void unring_2d (fftw_complex* data1,fftw_complex* tmp2, const size_t* dim_sz, si class ComputeSlice { public: - ComputeSlice (const vector& outer_axes, const vector& slice_axes, const size_t& nsh, const size_t& minW, const size_t& maxW, Image& in, Image& out) : + ComputeSlice (const vector& outer_axes, const vector& slice_axes, const int& nsh, const int& minW, const int& maxW, Image& in, Image& out) : outer_axes (outer_axes), slice_axes (slice_axes), nsh (nsh), @@ -249,31 +251,31 @@ class ComputeSlice void operator() (const Iterator& pos) { assign_pos_of (pos, outer_axes).to (in, out); - size_t dims[2]; + int dims[2]; dims[0] = in.size(slice_axes[0]); dims[1] = in.size(slice_axes[1]); - fftw_complex* in_complex = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*dims[0]*dims[1]); - size_t i = 0; + fftw_complex* in_complex = (fftw_complex*) fftw_malloc (sizeof(fftw_complex)*dims[0]*dims[1]); + int i = 0; for (auto l = Loop (slice_axes) (in); l; ++l) { in_complex[i][0] = in.value(); - in_complex[i++][1] = 0; + in_complex[i++][1] = 0.0; } - fftw_complex* out_complex = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*dims[0]*dims[1]); - unring_2d(in_complex,out_complex,dims,nsh,minW,maxW); - fftw_free(in_complex); + fftw_complex* out_complex = (fftw_complex*) fftw_malloc (sizeof(fftw_complex)*dims[0]*dims[1]); + unring_2d (in_complex,out_complex,dims,nsh,minW,maxW); + fftw_free (in_complex); i = 0; for (auto l = Loop (slice_axes) (out); l; ++l) out.value() = out_complex[i++][0]; - fftw_free(out_complex); + fftw_free (out_complex); } private: const vector& outer_axes; const vector& slice_axes; - const size_t nsh; - const size_t minW; - const size_t maxW; + const int nsh; + const int minW; + const int maxW; Image in, out; }; From 8008da68db7a471a9ff9df1ccacf1ccff4227ebc Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Tue, 1 Aug 2017 22:56:44 +0100 Subject: [PATCH 118/139] mrdegibbs: use full precision Math::pi This requires explicit handling of potential divide-by-zero later in the code. --- cmd/mrdegibbs.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/cmd/mrdegibbs.cpp b/cmd/mrdegibbs.cpp index 45effaa73d..fe00a06bdf 100644 --- a/cmd/mrdegibbs.cpp +++ b/cmd/mrdegibbs.cpp @@ -48,7 +48,6 @@ void usage () typedef double value_type; -constexpr double pi = 3.1416; void unring_1d (fftw_complex* data, int n, int numlines, int nsh, int minW, int maxW) @@ -76,7 +75,7 @@ void unring_1d (fftw_complex* data, int n, int numlines, int nsh, int minW, int int maxn = (n%2==1) ? (n-1)/2 : n/2-1; for (int j = 1; j < 2*nsh+1; j++) { - double phi = pi/double(n) * double(shifts[j])/double(nsh); + double phi = Math::pi/double(n) * double(shifts[j])/double(nsh); fftw_complex u = {cos(phi),sin(phi)}; fftw_complex e = {1,0}; sh[j*n ][0] = sh[0][0]; @@ -200,13 +199,17 @@ void unring_2d (fftw_complex* data1, fftw_complex* tmp2, const int* dim_sz, int fftw_execute_dft(p_tr,data2,tmp2); for (int k = 0; k < dim_sz[1]; k++) { - double ck = (1+cos(2*pi*(double(k)/dim_sz[1])))*0.5; + double ck = (1.0+cos(2.0*Math::pi*(double(k)/dim_sz[1])))*0.5; for (int j = 0 ; j < dim_sz[0]; j++) { - double cj = (1+cos(2.0*pi*(double(j)/dim_sz[0])))*0.5; - tmp1[k*dim_sz[0]+j][0] = nfac*(tmp1[k*dim_sz[0]+j][0] * ck) / (ck+cj); - tmp1[k*dim_sz[0]+j][1] = nfac*(tmp1[k*dim_sz[0]+j][1] * ck) / (ck+cj); - tmp2[j*dim_sz[1]+k][0] = nfac*(tmp2[j*dim_sz[1]+k][0] * cj) / (ck+cj); - tmp2[j*dim_sz[1]+k][1] = nfac*(tmp2[j*dim_sz[1]+k][1] * cj) / (ck+cj); + double cj = (1.0+cos(2.0*Math::pi*(double(j)/dim_sz[0])))*0.5; + if (ck + cj != 0.0) { + tmp1[k*dim_sz[0]+j][0] = nfac*(tmp1[k*dim_sz[0]+j][0] * ck) / (ck+cj); + tmp1[k*dim_sz[0]+j][1] = nfac*(tmp1[k*dim_sz[0]+j][1] * ck) / (ck+cj); + tmp2[j*dim_sz[1]+k][0] = nfac*(tmp2[j*dim_sz[1]+k][0] * cj) / (ck+cj); + tmp2[j*dim_sz[1]+k][1] = nfac*(tmp2[j*dim_sz[1]+k][1] * cj) / (ck+cj); + } + else + tmp1[k*dim_sz[0]+j][0] = tmp1[k*dim_sz[0]+j][1] = tmp2[j*dim_sz[1]+k][0] = tmp2[j*dim_sz[1]+k][1] = 0.0; } } From 018b729b970710a949185c9f55c2970898dc5f2d Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Wed, 2 Aug 2017 11:03:02 +1000 Subject: [PATCH 119/139] userdocs update --- docs/reference/commands/mrtransform.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/reference/commands/mrtransform.rst b/docs/reference/commands/mrtransform.rst index 409b58ac88..6cee9b69b0 100644 --- a/docs/reference/commands/mrtransform.rst +++ b/docs/reference/commands/mrtransform.rst @@ -54,6 +54,8 @@ Regridding options - **-interp method** set the interpolation method to use when reslicing (choices: nearest, linear, cubic, sinc. Default: cubic). +- **-oversample factor** set the amount of over-sampling (in the target space) to perform when regridding. This is particularly relevant when downsamping a high-resolution image to a low-resolution image, to avoid aliasing artefacts. This can consist of a single integer, or a comma-separated list of 3 integers if different oversampling factors are desired along the different axes. Default is determined from ratio of voxel dimensions (disabled for nearest-neighbour interpolation). + Non-linear transformation options ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 47832806a565ff01ee12990fa4d6be895dd800c9 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 2 Aug 2017 18:18:19 +1000 Subject: [PATCH 120/139] dwipreproc: Fix volume matching with zero-length gradient vectors --- bin/dwipreproc | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/bin/dwipreproc b/bin/dwipreproc index 2654886b4c..9b51f8b6da 100755 --- a/bin/dwipreproc +++ b/bin/dwipreproc @@ -184,8 +184,17 @@ do_topup = (not PE_design == 'None') def grads_match(one, two): # Dot product between gradient directions - dot_product = one[0]*two[0] + one[1]*two[1] + one[2]*two[2] - if abs(dot_product) < 0.999: + # First, need to check for zero-norm vectors: + # - If both are zero, skip this check + # - If one is zero and the other is not, volumes don't match + # - If neither is zero, test the dot product + if any([val for val in one[0:3]]): + if not any([val for val in two[0:3]]): + return False + dot_product = one[0]*two[0] + one[1]*two[1] + one[2]*two[2] + if abs(dot_product) < 0.999: + return False + elif any([val for val in two[0:3]]): return False # b-value if abs(one[3]-two[3]) > 10.0: @@ -243,6 +252,7 @@ if manual_pe_dir: app.error('If using -rpe_all option, input image must contain an even number of volumes') grads_matched = [ num_volumes ] * num_volumes grad_pairs = [ ] + app.debug('Commencing gradient direction matching; ' + str(num_volumes) + ' volumes') for index1 in range(int(num_volumes/2)): if grads_matched[index1] == num_volumes: # As yet unpaired for index2 in range(int(num_volumes/2), num_volumes): @@ -251,9 +261,10 @@ if manual_pe_dir: grads_matched[index1] = index2; grads_matched[index2] = index1; grad_pairs.append([index1, index2]) + app.debug('Matched volume ' + str(index1) + ' with ' + str(index2) + ': ' + str(grad[index1]) + ' ' + str(grad[index2])) break else: - app.error('Unable to determine matching reversed phase-encode direction volume for DWI volume ' + index1) + app.error('Unable to determine matching reversed phase-encode direction volume for DWI volume ' + str(index1)) if not len(grad_pairs) == num_volumes/2: app.error('Unable to determine complete matching DWI volume pairs for reversed phase-encode combination') # Construct manual PE scheme here: @@ -523,6 +534,7 @@ if not os.path.isfile(bvecs_path): # The phase-encoding scheme needs to be checked also volume_matchings = [ num_volumes ] * num_volumes volume_pairs = [ ] +app.debug('Commencing gradient direction matching; ' + str(num_volumes) + ' volumes') for index1 in range(num_volumes): if volume_matchings[index1] == num_volumes: # As yet unpaired for index2 in range(index1+1, num_volumes): @@ -532,10 +544,12 @@ for index1 in range(num_volumes): volume_matchings[index1] = index2; volume_matchings[index2] = index1; volume_pairs.append([index1, index2]) + app.debug('Matched volume ' + str(index1) + ' with ' + str(index2) + '\n' + + 'Phase encoding: ' + str(dwi_pe_scheme[index1]) + ' ' + str(dwi_pe_scheme[index2]) + '\n' + + 'Gradients: ' + str(grad[index1]) + ' ' + str(grad[index2])) break - if not len(volume_pairs) == int(num_volumes/2): # Convert the resulting volume to the output image, and re-insert the diffusion encoding From 3c6dbd357616f99eceaf5dc3891893850b458533 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Wed, 2 Aug 2017 15:01:44 +0100 Subject: [PATCH 121/139] mrdegibbs: removed FFTW dependency --- cmd/mrdegibbs.cpp | 369 +++++++++++++++++++++------------------------- 1 file changed, 171 insertions(+), 198 deletions(-) diff --git a/cmd/mrdegibbs.cpp b/cmd/mrdegibbs.cpp index fe00a06bdf..295a96d55e 100644 --- a/cmd/mrdegibbs.cpp +++ b/cmd/mrdegibbs.cpp @@ -11,13 +11,13 @@ * For more details, see http://www.mrtrix.org/. */ +#include #include "command.h" #include "progressbar.h" #include "image.h" #include "algo/threaded_loop.h" #include -#include using namespace MR; using namespace App; @@ -50,236 +50,209 @@ void usage () typedef double value_type; -void unring_1d (fftw_complex* data, int n, int numlines, int nsh, int minW, int maxW) -{ - fftw_complex* in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n); - fftw_complex* out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n); - fftw_plan p = fftw_plan_dft_1d(n, in, out, FFTW_FORWARD , FFTW_ESTIMATE); - fftw_plan pinv = fftw_plan_dft_1d(n, in, out, FFTW_BACKWARD, FFTW_ESTIMATE); - fftw_complex* sh = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n*(2*nsh+1)); - fftw_complex* sh2 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*n*(2*nsh+1)); - - double nfac = 1/double(n); - int* shifts = (int*) malloc(sizeof(int)*(2*nsh+1)); - shifts[0] = 0; - for (int j = 0; j < nsh; j++) { - shifts[j+1] = j+1; - shifts[1+nsh+j] = -(j+1); - } - - double TV1arr[2*nsh+1]; - double TV2arr[2*nsh+1]; - for (int k = 0; k < numlines; k++) { - fftw_execute_dft(p, &(data[n*k]), sh); - int maxn = (n%2==1) ? (n-1)/2 : n/2-1; - for (int j = 1; j < 2*nsh+1; j++) { - double phi = Math::pi/double(n) * double(shifts[j])/double(nsh); - fftw_complex u = {cos(phi),sin(phi)}; - fftw_complex e = {1,0}; - sh[j*n ][0] = sh[0][0]; - sh[j*n ][1] = sh[0][1]; - - if (n%2 == 0) { - sh[j*n + n/2][0] = 0; - sh[j*n + n/2][1] = 0; - } +class ComputeSlice +{ + public: + ComputeSlice (const vector& outer_axes, const vector& slice_axes, const int& nsh, const int& minW, const int& maxW, Image& in, Image& out) : + outer_axes (outer_axes), + slice_axes (slice_axes), + nsh (nsh), + minW (minW), + maxW (maxW), + X (slice_axes[0]), + Y (slice_axes[1]), + in (in), + out (out), + im1 (in.size(X), in.size(Y)), + im2 (im1.rows(), im1.cols()), + ft1 (im1.rows(), im1.cols()), + ft2 (im1.rows(), im1.cols()) { } - for (int l = 0; l < maxn; l++) { - double tmp = e[0]; - e[0] = u[0]*e[0] - u[1]*e[1]; - e[1] = tmp*u[1] + u[0]*e[1]; + void operator() (const Iterator& pos) + { + assign_pos_of (pos, outer_axes).to (in, out); - int L = l+1; - sh[j*n +L][0] = (e[0]*sh[L][0] - e[1]*sh[L][1]); - sh[j*n +L][1] = (e[0]*sh[L][1] + e[1]*sh[L][0]); - L = n-1-l; - sh[j*n +L][0] = (e[0]*sh[L][0] + e[1]*sh[L][1]); - sh[j*n +L][1] = (e[0]*sh[L][1] - e[1]*sh[L][0]); + for (auto l = Loop (slice_axes) (in); l; ++l) + im1 (in.index(X), in.index(Y)) = cdouble (in.value(), 0.0); + + unring_2d (); - } + for (auto l = Loop (slice_axes) (out); l; ++l) + out.value() = im1 (out.index(X), out.index(Y)).real(); } + private: + const vector& outer_axes; + const vector& slice_axes; + const int nsh, minW, maxW, X, Y; + Image in, out; + Eigen::FFT fftx, ffty; + Eigen::MatrixXcd im1, im2, ft1, ft2, shifted; + Eigen::VectorXcd vx, vy; - for (int j = 0; j < 2*nsh+1; ++j) - fftw_execute_dft(pinv, &(sh[j*n]), &sh2[j*n]); - - for (int j = 0; j < 2*nsh+1; ++j) { - TV1arr[j] = 0; - TV2arr[j] = 0; - const int l = 0; - for (int t = minW; t <= maxW; t++) { - TV1arr[j] += fabs(sh2[j*n + (l-t+n)%n ][0] - sh2[j*n + (l-(t+1)+n)%n ][0]); - TV1arr[j] += fabs(sh2[j*n + (l-t+n)%n ][1] - sh2[j*n + (l-(t+1)+n)%n ][1]); - TV2arr[j] += fabs(sh2[j*n + (l+t+n)%n ][0] - sh2[j*n + (l+(t+1)+n)%n ][0]); - TV2arr[j] += fabs(sh2[j*n + (l+t+n)%n ][1] - sh2[j*n + (l+(t+1)+n)%n ][1]); + void FFT_2D (const Eigen::MatrixXcd& in, Eigen::MatrixXcd& out) + { + for (auto n = 0; n < in.rows(); ++n) { + fftx.fwd (vx, in.row(n)); + out.row(n) = vx; } - } - - for(int l=0; l < n; l++) { - double minTV = std::numeric_limits::max(); - int minidx = 0; - for (int j = 0; j < 2*nsh+1; ++j) { - - if (TV1arr[j] < minTV) { - minTV = TV1arr[j]; - minidx = j; - } - if (TV2arr[j] < minTV) { - minTV = TV2arr[j]; - minidx = j; - } - - TV1arr[j] += fabs(sh2[j*n + (l-minW+1+n)%n ][0] - sh2[j*n + (l-(minW)+n)%n ][0]); - TV1arr[j] -= fabs(sh2[j*n + (l-maxW+n)%n ][0] - sh2[j*n + (l-(maxW+1)+n)%n ][0]); - TV2arr[j] += fabs(sh2[j*n + (l+maxW+1+n)%n ][0] - sh2[j*n + (l+(maxW+2)+n)%n ][0]); - TV2arr[j] -= fabs(sh2[j*n + (l+minW+n)%n ][0] - sh2[j*n + (l+(minW+1)+n)%n ][0]); - - TV1arr[j] += fabs(sh2[j*n + (l-minW+1+n)%n ][1] - sh2[j*n + (l-(minW)+n)%n ][1]); - TV1arr[j] -= fabs(sh2[j*n + (l-maxW+n)%n ][1] - sh2[j*n + (l-(maxW+1)+n)%n ][1]); - TV2arr[j] += fabs(sh2[j*n + (l+maxW+1+n)%n ][1] - sh2[j*n + (l+(maxW+2)+n)%n ][1]); - TV2arr[j] -= fabs(sh2[j*n + (l+minW+n)%n ][1] - sh2[j*n + (l+(minW+1)+n)%n ][1]); + for (auto n = 0; n < out.cols(); ++n) { + ffty.fwd (vy, out.col(n)); + out.col(n) = vy; } + } - double a0r = sh2[minidx*n + (l-1+n)%n ][0]; - double a1r = sh2[minidx*n + l][0]; - double a2r = sh2[minidx*n + (l+1+n)%n ][0]; - double a0i = sh2[minidx*n + (l-1+n)%n ][1]; - double a1i = sh2[minidx*n + l][1]; - double a2i = sh2[minidx*n + (l+1+n)%n ][1]; - double s = double(shifts[minidx])/nsh/2; - - if (s>0) { - data[k*n + l][0] = (a1r*(1-s) + a0r*s)*nfac; - data[k*n + l][1] = (a1i*(1-s) + a0i*s)*nfac; + void FFT_2D_inv (const Eigen::MatrixXcd& in, Eigen::MatrixXcd& out) + { + for (auto n = 0; n < in.rows(); ++n) { + fftx.inv (vx, in.row(n)); + out.row(n) = vx; } - else { - s = -s; - data[k*n + l][0] = (a1r*(1-s) + a2r*s)*nfac; - data[k*n + l][1] = (a1i*(1-s) + a2i*s)*nfac; + for (auto n = 0; n < out.cols(); ++n) { + ffty.inv (vy, out.col(n)); + out.col(n) = vy; } } - } - free(shifts); - fftw_destroy_plan(p); - fftw_destroy_plan(pinv); - fftw_free(in); - fftw_free(out); - fftw_free(sh); - fftw_free(sh2); -} - -void unring_2d (fftw_complex* data1, fftw_complex* tmp2, const int* dim_sz, int nsh, int minW, int maxW) -{ - fftw_complex* tmp1 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * dim_sz[0]*dim_sz[1]); - fftw_complex* data2 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * dim_sz[0]*dim_sz[1]); - - fftw_plan p = fftw_plan_dft_2d (dim_sz[1],dim_sz[0], data1, tmp1, FFTW_FORWARD, FFTW_ESTIMATE); - fftw_plan pinv = fftw_plan_dft_2d (dim_sz[1],dim_sz[0], data1, tmp1, FFTW_BACKWARD, FFTW_ESTIMATE); - fftw_plan p_tr = fftw_plan_dft_2d (dim_sz[0],dim_sz[1], data2, tmp2, FFTW_FORWARD, FFTW_ESTIMATE); - fftw_plan pinv_tr = fftw_plan_dft_2d (dim_sz[0],dim_sz[1], data2, tmp2, FFTW_BACKWARD, FFTW_ESTIMATE); - - double nfac = 1/double(dim_sz[0]*dim_sz[1]); - - for (int k = 0; k < dim_sz[1]; k++) { - for (int j = 0; j < dim_sz[0]; j++) { - data2[j*dim_sz[1]+k][0] = data1[k*dim_sz[0]+j][0]; - data2[j*dim_sz[1]+k][1] = data1[k*dim_sz[0]+j][1]; - } - } - - fftw_execute_dft(p,data1,tmp1); - fftw_execute_dft(p_tr,data2,tmp2); - - for (int k = 0; k < dim_sz[1]; k++) { - double ck = (1.0+cos(2.0*Math::pi*(double(k)/dim_sz[1])))*0.5; - for (int j = 0 ; j < dim_sz[0]; j++) { - double cj = (1.0+cos(2.0*Math::pi*(double(j)/dim_sz[0])))*0.5; - if (ck + cj != 0.0) { - tmp1[k*dim_sz[0]+j][0] = nfac*(tmp1[k*dim_sz[0]+j][0] * ck) / (ck+cj); - tmp1[k*dim_sz[0]+j][1] = nfac*(tmp1[k*dim_sz[0]+j][1] * ck) / (ck+cj); - tmp2[j*dim_sz[1]+k][0] = nfac*(tmp2[j*dim_sz[1]+k][0] * cj) / (ck+cj); - tmp2[j*dim_sz[1]+k][1] = nfac*(tmp2[j*dim_sz[1]+k][1] * cj) / (ck+cj); + void unring_2d () + { + FFT_2D (im1, ft1); + + for (int k = 0; k < im1.cols(); k++) { + double ck = (1.0+cos(2.0*Math::pi*(double(k)/im1.cols())))*0.5; + for (int j = 0 ; j < im1.rows(); j++) { + double cj = (1.0+cos(2.0*Math::pi*(double(j)/im1.rows())))*0.5; + + if (ck+cj != 0.0) { + ft2(j,k) = ft1(j,k) * cj / (ck+cj); + ft1(j,k) *= ck / (ck+cj); + } + else + ft1(j,k) = ft2(j,k) = cdouble(0.0, 0.0); + } } - else - tmp1[k*dim_sz[0]+j][0] = tmp1[k*dim_sz[0]+j][1] = tmp2[j*dim_sz[1]+k][0] = tmp2[j*dim_sz[1]+k][1] = 0.0; - } - } - fftw_execute_dft(pinv,tmp1,data1); - fftw_execute_dft(pinv_tr,tmp2,data2); + FFT_2D_inv (ft1, im1); + FFT_2D_inv (ft2, im2); - unring_1d(data1,dim_sz[0],dim_sz[1],nsh,minW,maxW); - unring_1d(data2,dim_sz[1],dim_sz[0],nsh,minW,maxW); + unring_1d (im1); + auto im2_t = im2.transpose(); + unring_1d (im2_t); - fftw_execute_dft(p,data1,tmp1); - fftw_execute_dft(p_tr,data2,tmp2); - - for (int k = 0; k < dim_sz[1]; k++) { - for (int j = 0; j < dim_sz[0]; j++) { - tmp1[k*dim_sz[0]+j][0] = nfac*(tmp1[k*dim_sz[0]+j][0] + tmp2[j*dim_sz[1]+k][0]); - tmp1[k*dim_sz[0]+j][1] = nfac*(tmp1[k*dim_sz[0]+j][1] + tmp2[j*dim_sz[1]+k][1]); - } - } - - fftw_execute_dft(pinv,tmp1,tmp2); - - fftw_free(data2); - fftw_free(tmp1); -} + FFT_2D (im1, ft1); + FFT_2D (im2, ft2); + ft1 += ft2; + FFT_2D_inv (ft1, im1); + } + template + void unring_1d (Eigen::MatrixBase& eig) + { + const int n = eig.rows(); + const int numlines = eig.cols(); + shifted.resize (n, 2*nsh+1); -class ComputeSlice -{ - public: - ComputeSlice (const vector& outer_axes, const vector& slice_axes, const int& nsh, const int& minW, const int& maxW, Image& in, Image& out) : - outer_axes (outer_axes), - slice_axes (slice_axes), - nsh (nsh), - minW (minW), - maxW (maxW), - in (in), - out (out) { } + int shifts [2*nsh+1]; + shifts[0] = 0; + for (int j = 0; j < nsh; j++) { + shifts[j+1] = j+1; + shifts[1+nsh+j] = -(j+1); + } - void operator() (const Iterator& pos) - { - assign_pos_of (pos, outer_axes).to (in, out); - int dims[2]; - dims[0] = in.size(slice_axes[0]); - dims[1] = in.size(slice_axes[1]); - fftw_complex* in_complex = (fftw_complex*) fftw_malloc (sizeof(fftw_complex)*dims[0]*dims[1]); - int i = 0; - for (auto l = Loop (slice_axes) (in); l; ++l) { - in_complex[i][0] = in.value(); - in_complex[i++][1] = 0.0; + double TV1arr[2*nsh+1]; + double TV2arr[2*nsh+1]; + + for (int k = 0; k < numlines; k++) { + fftx.fwd (vx, eig.col(k)); + shifted.col(0) = vx; + + const int maxn = (n&1) ? (n-1)/2 : n/2-1; + + for (int j = 1; j < 2*nsh+1; j++) { + double phi = Math::pi*double(shifts[j])/double(n*nsh); + cdouble u (std::cos(phi), std::sin(phi)); + cdouble e (1.0, 0.0); + shifted(0,j) = shifted(0,0); + + if (!(n&1)) + shifted(n/2,j) = cdouble(0.0, 0.0); + + for (int l = 0; l < maxn; l++) { + e = u*e; + int L = l+1; shifted(L,j) = e * shifted(L,0); + L = n-1-l; shifted(L,j) = std::conj(e) * shifted(L,0); + } + } + + + for (int j = 0; j < 2*nsh+1; ++j) { + fftx.inv (vx, shifted.col(j)); + shifted.col(j) = vx; + } + + for (int j = 0; j < 2*nsh+1; ++j) { + TV1arr[j] = 0.0; + TV2arr[j] = 0.0; + for (int t = minW; t <= maxW; t++) { + TV1arr[j] += std::abs (shifted((n-t)%n,j).real() - shifted((n-t-1)%n,j).real()); + TV1arr[j] += std::abs (shifted((n-t)%n,j).imag() - shifted((n-t-1)%n,j).imag()); + TV2arr[j] += std::abs (shifted((n+t)%n,j).real() - shifted((n+t+1)%n,j).real()); + TV2arr[j] += std::abs (shifted((n+t)%n,j).imag() - shifted((n+t+1)%n,j).imag()); + } + } + + for (int l = 0; l < n; ++l) { + double minTV = std::numeric_limits::max(); + int minidx = 0; + for (int j = 0; j < 2*nsh+1; ++j) { + + if (TV1arr[j] < minTV) { + minTV = TV1arr[j]; + minidx = j; + } + if (TV2arr[j] < minTV) { + minTV = TV2arr[j]; + minidx = j; + } + + TV1arr[j] += std::abs (shifted((l-minW+1+n)%n,j).real() - shifted((l-(minW )+n)%n,j).real()); + TV1arr[j] -= std::abs (shifted((l-maxW +n)%n,j).real() - shifted((l-(maxW+1)+n)%n,j).real()); + TV2arr[j] += std::abs (shifted((l+maxW+1+n)%n,j).real() - shifted((l+(maxW+2)+n)%n,j).real()); + TV2arr[j] -= std::abs (shifted((l+minW +n)%n,j).real() - shifted((l+(minW+1)+n)%n,j).real()); + + TV1arr[j] += std::abs (shifted((l-minW+1+n)%n,j).imag() - shifted((l-(minW )+n)%n,j).imag()); + TV1arr[j] -= std::abs (shifted((l-maxW +n)%n,j).imag() - shifted((l-(maxW+1)+n)%n,j).imag()); + TV2arr[j] += std::abs (shifted((l+maxW+1+n)%n,j).imag() - shifted((l+(maxW+2)+n)%n,j).imag()); + TV2arr[j] -= std::abs (shifted((l+minW +n)%n,j).imag() - shifted((l+(minW+1)+n)%n,j).imag()); + } + + double a0r = shifted((l-1+n)%n,minidx).real(); + double a1r = shifted(l,minidx).real(); + double a2r = shifted((l+1+n)%n,minidx).real(); + double a0i = shifted((l-1+n)%n,minidx).imag(); + double a1i = shifted(l,minidx).imag(); + double a2i = shifted((l+1+n)%n,minidx).imag(); + double s = double(shifts[minidx])/(2.0*nsh); + + if (s > 0.0) + eig(l,k) = cdouble (a1r*(1.0-s) + a0r*s, a1i*(1.0-s) + a0i*s); + else + eig(l,k) = cdouble (a1r*(1.0+s) - a2r*s, a1i*(1.0+s) - a2i*s); + } + } } - fftw_complex* out_complex = (fftw_complex*) fftw_malloc (sizeof(fftw_complex)*dims[0]*dims[1]); - unring_2d (in_complex,out_complex,dims,nsh,minW,maxW); - fftw_free (in_complex); - i = 0; - for (auto l = Loop (slice_axes) (out); l; ++l) - out.value() = out_complex[i++][0]; - fftw_free (out_complex); - } - private: - const vector& outer_axes; - const vector& slice_axes; - const int nsh; - const int minW; - const int maxW; - Image in, out; }; From d935cd991e3b7a730ebff16ac99cd82362d6ebe9 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Wed, 2 Aug 2017 15:34:16 +0100 Subject: [PATCH 122/139] mrdegibbs: substantial simplication and avoidance of redundant FFTs --- cmd/mrdegibbs.cpp | 80 +++++++++++++++++------------------------------ 1 file changed, 28 insertions(+), 52 deletions(-) diff --git a/cmd/mrdegibbs.cpp b/cmd/mrdegibbs.cpp index 295a96d55e..4f96fa8f38 100644 --- a/cmd/mrdegibbs.cpp +++ b/cmd/mrdegibbs.cpp @@ -66,9 +66,7 @@ class ComputeSlice in (in), out (out), im1 (in.size(X), in.size(Y)), - im2 (im1.rows(), im1.cols()), - ft1 (im1.rows(), im1.cols()), - ft2 (im1.rows(), im1.cols()) { } + im2 (im1.rows(), im1.cols()) { } void operator() (const Iterator& pos) { @@ -89,41 +87,24 @@ class ComputeSlice const int nsh, minW, maxW, X, Y; Image in, out; Eigen::FFT fftx, ffty; - Eigen::MatrixXcd im1, im2, ft1, ft2, shifted; - Eigen::VectorXcd vx, vy; + Eigen::MatrixXcd im1, im2, shifted; + Eigen::VectorXcd v; - void FFT_2D (const Eigen::MatrixXcd& in, Eigen::MatrixXcd& out) - { - for (auto n = 0; n < in.rows(); ++n) { - fftx.fwd (vx, in.row(n)); - out.row(n) = vx; - } - for (auto n = 0; n < out.cols(); ++n) { - ffty.fwd (vy, out.col(n)); - out.col(n) = vy; - } - } - - void FFT_2D_inv (const Eigen::MatrixXcd& in, Eigen::MatrixXcd& out) - { - for (auto n = 0; n < in.rows(); ++n) { - fftx.inv (vx, in.row(n)); - out.row(n) = vx; - } - for (auto n = 0; n < out.cols(); ++n) { - ffty.inv (vy, out.col(n)); - out.col(n) = vy; - } - } + template FORCE_INLINE void FFT (Eigen::MatrixBase&& vec) { fftx.fwd (v, vec); vec = v; } + template FORCE_INLINE void FFT (Eigen::MatrixBase& vec) { FFT (std::move (vec)); } + template FORCE_INLINE void iFFT (Eigen::MatrixBase&& vec) { fftx.inv (v, vec); vec = v; } + template FORCE_INLINE void iFFT (Eigen::MatrixBase& vec) { iFFT (std::move (vec)); } + template FORCE_INLINE void row_FFT (Eigen::MatrixBase& mat) { for (auto n = 0; n < mat.rows(); ++n) FFT (mat.row(n)); } + template FORCE_INLINE void row_iFFT (Eigen::MatrixBase& mat) { for (auto n = 0; n < mat.rows(); ++n) iFFT (mat.row(n)); } + template FORCE_INLINE void col_FFT (Eigen::MatrixBase& mat) { for (auto n = 0; n < mat.cols(); ++n) FFT (mat.col(n)); } + template FORCE_INLINE void col_iFFT (Eigen::MatrixBase& mat) { for (auto n = 0; n < mat.cols(); ++n) iFFT (mat.col(n)); } - - - - void unring_2d () + FORCE_INLINE void unring_2d () { - FFT_2D (im1, ft1); + row_FFT (im1); + col_FFT (im1); for (int k = 0; k < im1.cols(); k++) { double ck = (1.0+cos(2.0*Math::pi*(double(k)/im1.cols())))*0.5; @@ -131,32 +112,29 @@ class ComputeSlice double cj = (1.0+cos(2.0*Math::pi*(double(j)/im1.rows())))*0.5; if (ck+cj != 0.0) { - ft2(j,k) = ft1(j,k) * cj / (ck+cj); - ft1(j,k) *= ck / (ck+cj); + im2(j,k) = im1(j,k) * cj / (ck+cj); + im1(j,k) *= ck / (ck+cj); } else - ft1(j,k) = ft2(j,k) = cdouble(0.0, 0.0); + im1(j,k) = im2(j,k) = cdouble(0.0, 0.0); } } - FFT_2D_inv (ft1, im1); - FFT_2D_inv (ft2, im2); + row_iFFT (im1); + col_iFFT (im2); unring_1d (im1); - auto im2_t = im2.transpose(); - unring_1d (im2_t); + unring_1d (im2.transpose()); + + im1 += im2; + } - FFT_2D (im1, ft1); - FFT_2D (im2, ft2); - ft1 += ft2; - FFT_2D_inv (ft1, im1); - } template - void unring_1d (Eigen::MatrixBase& eig) + FORCE_INLINE void unring_1d (Eigen::MatrixBase&& eig) { const int n = eig.rows(); const int numlines = eig.cols(); @@ -173,8 +151,7 @@ class ComputeSlice double TV2arr[2*nsh+1]; for (int k = 0; k < numlines; k++) { - fftx.fwd (vx, eig.col(k)); - shifted.col(0) = vx; + shifted.col(0) = eig.col(k); const int maxn = (n&1) ? (n-1)/2 : n/2-1; @@ -195,10 +172,7 @@ class ComputeSlice } - for (int j = 0; j < 2*nsh+1; ++j) { - fftx.inv (vx, shifted.col(j)); - shifted.col(j) = vx; - } + col_iFFT (shifted); for (int j = 0; j < 2*nsh+1; ++j) { TV1arr[j] = 0.0; @@ -252,6 +226,8 @@ class ComputeSlice } } + template + FORCE_INLINE void unring_1d (Eigen::MatrixBase& eig) { unring_1d (std::move (eig)); } }; From 33a6b092bd319a6d9d1580b4fff34b98cb8ce4c7 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Wed, 2 Aug 2017 16:11:08 +0100 Subject: [PATCH 123/139] mrdegibbs: add missing options and tidy up help page --- cmd/mrdegibbs.cpp | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/cmd/mrdegibbs.cpp b/cmd/mrdegibbs.cpp index 4f96fa8f38..1f679e2a81 100644 --- a/cmd/mrdegibbs.cpp +++ b/cmd/mrdegibbs.cpp @@ -24,12 +24,14 @@ using namespace App; void usage () { - AUTHOR = "Ben Jeurissen (ben.jeurissen@uantwerpen.be)"; + AUTHOR = "Ben Jeurissen (ben.jeurissen@uantwerpen.be) & J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Remove Gibbs Ringing Artifact"; DESCRIPTION - + "to be filled in"; + + "This application attempts to remove Gibbs ringing artefacts from MRI images using the method " + "of local subvoxel-shifts proposed by Kellner et al. (see reference below for details)."; + ARGUMENTS + Argument ("in", "the input image.").type_image_in () @@ -38,12 +40,26 @@ void usage () OPTIONS + Option ("axes", - "select the slice axes (default: 0,1 - i.e. x-y)") - + Argument ("list").type_sequence_int (); + "select the slice axes (default: 0,1 - i.e. x-y).") + + Argument ("list").type_sequence_int () + + + Option ("nshifts", "discretization of subpixel spacing (default: 20).") + + Argument ("value").type_integer (8, 128) + + + Option ("minW", "left border of window used for TV computation (default: 1).") + + Argument ("value").type_integer (0, 10) + + + Option ("maxW", "right border of window used for TV computation (default: 3).") + + Argument ("value").type_integer (0, 128) + + + DataType::options(); REFERENCES - + "Kellner ref"; + + "Kellner, E; Dhital, B; Kiselev, V.G & Reisert, M. " + "Gibbs-ringing artifact removal based on local subvoxel-shifts. " + "Magnetic Resonance in Medicine, 2016, 76, 1574–1581."; + } @@ -239,10 +255,17 @@ class ComputeSlice void run () { + const int nshifts = App::get_option_value ("nshifts", 20); + const int minW = App::get_option_value ("minW", 1); + const int maxW = App::get_option_value ("maxW", 3); + + if (minW >= maxW) + throw Exception ("minW must be smaller than maxW"); + auto in = Image::open (argument[0]); Header header (in); - header.datatype() = DataType::Float64; + header.datatype() = DataType::from_command_line (DataType::Float32); auto out = Image::create (argument[1], header); vector slice_axes = { 0, 1 }; @@ -264,7 +287,7 @@ void run () outer_axes.erase (it); } - ThreadedLoop ("computing stuff...", in, outer_axes, slice_axes) - .run_outer (ComputeSlice (outer_axes, slice_axes, 30, 1, 3, in, out)); + ThreadedLoop ("performing Gibbs ringing removal", in, outer_axes, slice_axes) + .run_outer (ComputeSlice (outer_axes, slice_axes, nshifts, minW, maxW, in, out)); } From c218b4c3ed25e0bc3547314335b36e66af678205 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Wed, 2 Aug 2017 21:32:02 +0100 Subject: [PATCH 124/139] mrdegibbs: fix up Eigen memory alignment --- cmd/mrdegibbs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/mrdegibbs.cpp b/cmd/mrdegibbs.cpp index 1f679e2a81..d5aaeff4d3 100644 --- a/cmd/mrdegibbs.cpp +++ b/cmd/mrdegibbs.cpp @@ -69,7 +69,7 @@ typedef double value_type; class ComputeSlice -{ +{ MEMALIGN (ComputeSlice) public: ComputeSlice (const vector& outer_axes, const vector& slice_axes, const int& nsh, const int& minW, const int& maxW, Image& in, Image& out) : outer_axes (outer_axes), From eb5c096f4683a5da482d756f3aba85d56c5616a9 Mon Sep 17 00:00:00 2001 From: Rami Tabbara Date: Thu, 3 Aug 2017 10:32:38 +1000 Subject: [PATCH 125/139] mrview: lightbox: Always update slice_focus_delta * Even when cycling through vols this needs to be updated --- src/gui/mrview/mode/lightbox.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/mrview/mode/lightbox.cpp b/src/gui/mrview/mode/lightbox.cpp index 3bbf01b04c..8cf63c185a 100644 --- a/src/gui/mrview/mode/lightbox.cpp +++ b/src/gui/mrview/mode/lightbox.cpp @@ -130,9 +130,10 @@ namespace MR const Eigen::Vector3f slice_focus = move_in_out_displacement(focus_delta, slice_proj); set_focus(focus() + slice_focus); - update_slices_focusdelta(); } else if (volume_indices[slice_index] == -1) current_slice_index = prev_index; + + update_slices_focusdelta(); } void LightBox::update_slices_focusdelta() From 0f25e63b8f154392b1457635afea746c9759bd99 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 3 Aug 2017 15:13:59 +1000 Subject: [PATCH 126/139] mrtrix3.fsl.findImage(): New function This helper function should be used to locate output images from FSL commands, due to the fact that not all FSL commands / software versions honour the FSLOUTPUTTYPE environment variable. --- bin/dwibiascorrect | 8 +++---- bin/dwipreproc | 15 +++++++------ bin/labelsgmfix | 2 +- lib/mrtrix3/_5ttgen/fsl.py | 31 +++++++++++++-------------- lib/mrtrix3/fsl.py | 44 +++++++++++++++++++++++++++++++------- 5 files changed, 63 insertions(+), 37 deletions(-) diff --git a/bin/dwibiascorrect b/bin/dwibiascorrect index 7fc3ac307f..be497005a6 100755 --- a/bin/dwibiascorrect +++ b/bin/dwibiascorrect @@ -51,10 +51,6 @@ if app.args.fsl: app.error('Could not find FSL program fast; please verify FSL install') fsl_suffix = fsl.suffix() - if fast_cmd == 'fast': - fast_suffix = fsl_suffix - else: - fast_suffix = '.nii.gz' elif app.args.ants: @@ -101,10 +97,12 @@ else: run.command('dwiextract in.mif - -bzero | mrmath - mean mean_bzero.mif -axis 3') if app.args.fsl: + # FAST doesn't accept a mask input; therefore need to explicitly mask the input image run.command('mrcalc mean_bzero.mif mask.mif -mult - | mrconvert - mean_bzero_masked.nii -stride -1,+2,+3') run.command(fast_cmd + ' -t 2 -o fast -n 3 -b mean_bzero_masked.nii') - bias_path = 'fast_bias' + fast_suffix + bias_path = fsl.findImage('fast_bias') + elif app.args.ants: # If the mask image was provided manually, and doesn't match the input image perfectly diff --git a/bin/dwipreproc b/bin/dwipreproc index 2654886b4c..af5461209f 100755 --- a/bin/dwipreproc +++ b/bin/dwipreproc @@ -473,6 +473,7 @@ if do_topup: run.command('dwiextract dwi.mif' + import_dwi_pe_table_option + ' -pe ' + ','.join(line.split()) + ' - | mrconvert - ' + input_path + ' -json_export ' + json_path) run.command(applytopup_cmd + ' --imain=' + input_path + ' --datain=applytopup_config.txt --inindex=' + str(index) + ' --topup=field --out=' + temp_path + ' --method=jac') file.delTempFile(input_path) + temp_path = fsl.findImage(temp_path) run.command('mrconvert ' + temp_path + ' ' + output_path + ' -json_import ' + json_path) file.delTempFile(json_path) file.delTempFile(temp_path) @@ -539,7 +540,7 @@ for index1 in range(num_volumes): if not len(volume_pairs) == int(num_volumes/2): # Convert the resulting volume to the output image, and re-insert the diffusion encoding - run.command('mrconvert dwi_post_eddy' + fsl_suffix + ' result.mif' + stride_option + ' -fslgrad ' + bvecs_path + ' bvals') + run.command('mrconvert ' + fsl.findImage('dwi_post_eddy') + ' result.mif' + stride_option + ' -fslgrad ' + bvecs_path + ' bvals') else: app.console('Detected matching DWI volumes with opposing phase encoding; performing explicit volume recombination') @@ -569,7 +570,7 @@ else: # If one diffusion sensitisation gradient direction is reversed with respect to # the other, still want to enable their recombination; but need to explicitly # account for this when averaging the two directions - if norm2 < 0.999: + if norm2 < 0.0: 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]]) ] @@ -600,12 +601,12 @@ else: # Detect this, and manually replace the transform if necessary # (even if this doesn't cause an issue with the subsequent mrcalc command, it may in the future, it's better for # visualising the script temporary files, and it gives the user a warning about an out-of-date FSL) - field_map_image = 'field_map' + fsl_suffix + field_map_image = fsl.findImage('field_map') if not image.match('topup_in.nii', field_map_image): app.warn('topup output field image has erroneous header; recommend updating FSL to version 5.0.8 or later') - run.command('mrtransform ' + field_map_image + ' -replace topup_in.nii field_map_fix' + fsl_suffix) + run.command('mrtransform ' + field_map_image + ' -replace topup_in.nii field_map_fix.mif') file.delTempFile(field_map_image) - field_map_image = 'field_map_fix' + fsl_suffix + field_map_image = 'field_map_fix.mif' @@ -638,8 +639,8 @@ else: # If eddy provides its main image output in a compressed format, the code block below will need to # uncompress that image independently for every volume pair. Instead, if this is the case, let's # convert it to an uncompressed format before we do anything with it. - eddy_output = 'dwi_post_eddy' + fsl_suffix - if fsl_suffix.endswith('.gz'): + eddy_output = fsl.findImage('dwi_post_eddy') + if eddy_output.endswith('.gz'): run.command('mrconvert ' + eddy_output + ' dwi_post_eddy.nii') file.delTempFile(eddy_output) eddy_output = 'dwi_post_eddy.nii' diff --git a/bin/labelsgmfix b/bin/labelsgmfix index b1488b5913..2c87a17d74 100755 --- a/bin/labelsgmfix +++ b/bin/labelsgmfix @@ -83,7 +83,7 @@ if app.args.premasked: first_input_is_brain_extracted = ' -b' run.command(first_cmd + ' -s ' + ','.join(structure_map.keys()) + ' -i T1.nii' + first_input_is_brain_extracted + ' -o first') -# Generate an empty image that will be used to contruct the new SGM nodes +# Generate an empty image that will be used to construct the new SGM nodes run.command('mrcalc parc.mif 0 -min sgm.mif') # Read the local connectome LUT file diff --git a/lib/mrtrix3/_5ttgen/fsl.py b/lib/mrtrix3/_5ttgen/fsl.py index 9c25c883ed..b5011185c1 100644 --- a/lib/mrtrix3/_5ttgen/fsl.py +++ b/lib/mrtrix3/_5ttgen/fsl.py @@ -141,8 +141,8 @@ def execute(): file.waitFor('T1_preBET' + fsl_suffix) # BET - fast_t1_input = 'T1_BET' + fsl_suffix - run.command(bet_cmd + ' T1_preBET' + fsl_suffix + ' ' + fast_t1_input + ' -f 0.15 -R') + run.command(bet_cmd + ' T1_preBET' + fsl_suffix + ' T1_BET' + fsl_suffix + ' -f 0.15 -R') + fast_t1_input = fsl.findImage('T1_BET' + fsl_suffix) if os.path.exists('T2.nii'): if app.args.nocrop: @@ -159,7 +159,6 @@ def execute(): run.command(fast_cmd + ' -S 2 ' + fast_t2_input + ' ' + fast_t1_input) else: run.command(fast_cmd + ' ' + fast_t1_input) - fast_output_prefix = fast_t1_input.split('.')[0] # FIRST first_input_is_brain_extracted = '' @@ -192,23 +191,23 @@ def execute(): pve_cat = ' '.join(pve_image_list) run.command('mrmath ' + pve_cat + ' sum - | mrcalc - 1.0 -min all_sgms.mif') - # Looks like FAST in 5.0 ignores FSLOUTPUTTYPE when writing the PVE images - # Will have to wait and see whether this changes, and update the script accordingly - if fast_cmd == 'fast': - fast_suffix = fsl_suffix - else: - fast_suffix = '.nii.gz' - # Combine the tissue images into the 5TT format within the script itself + fast_output_prefix = fast_t1_input.split('.')[0] + fast_csf_output = fsl.findImage(fast_output_prefix + '_pve_0') + fast_gm_output = fsl.findImage(fast_output_prefix + '_pve_1') + fast_wm_output = fsl.findImage(fast_output_prefix + '_pve_2') # Step 1: Run LCC on the WM image - run.command('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 - run.command('mrcalc ' + fast_output_prefix + '_pve_0' + fast_suffix + ' remove_unconnected_wm_mask.mif -mult csf.mif') + run.command('mrthreshold ' + fast_wm_output + ' - -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 old 5ttgen binary used to: + # - Preserve CSF as-is + # - Preserve SGM, unless it results in a sum of volume fractions greater than 1, in which case clamp + # - Multiply the FAST volume fractions of GM and CSF, so that the sum of CSF, SGM, CGM and WM is 1.0 + run.command('mrcalc ' + fast_csf_output + ' remove_unconnected_wm_mask.mif -mult csf.mif') run.command('mrcalc 1.0 csf.mif -sub all_sgms.mif -min sgm.mif') - run.command('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') + run.command('mrcalc 1.0 csf.mif sgm.mif -add -sub ' + fast_gm_output + ' ' + fast_wm_output + ' -add -div multiplier.mif') run.command('mrcalc multiplier.mif -finite multiplier.mif 0.0 -if multiplier_noNAN.mif') - run.command('mrcalc ' + fast_output_prefix + '_pve_1' + fast_suffix + ' multiplier_noNAN.mif -mult remove_unconnected_wm_mask.mif -mult cgm.mif') - run.command('mrcalc ' + fast_output_prefix + '_pve_2' + fast_suffix + ' multiplier_noNAN.mif -mult remove_unconnected_wm_mask.mif -mult wm.mif') + run.command('mrcalc ' + fast_gm_output + ' multiplier_noNAN.mif -mult remove_unconnected_wm_mask.mif -mult cgm.mif') + run.command('mrcalc ' + fast_wm_output + ' multiplier_noNAN.mif -mult remove_unconnected_wm_mask.mif -mult wm.mif') run.command('mrcalc 0 wm.mif -min path.mif') run.command('mrcat cgm.mif sgm.mif wm.mif csf.mif path.mif - -axis 3 | mrconvert - combined_precrop.mif -stride +2,+3,+4,+1') diff --git a/lib/mrtrix3/fsl.py b/lib/mrtrix3/fsl.py index 45c132dad4..bc93e5aac3 100644 --- a/lib/mrtrix3/fsl.py +++ b/lib/mrtrix3/fsl.py @@ -1,3 +1,5 @@ +_suffix = '' + # Functions that may be useful for scripts that interface with FMRIB FSL tools # Get the name of the binary file that should be invoked to run eddy; @@ -27,6 +29,24 @@ def eddyBinary(cuda): +# In some versions of FSL, even though we try to predict the names of image files that +# FSL commands will generate based on the suffix() function, the FSL binaries themselves +# ignore the FSLOUTPUTTYPE environment variable. Therefore, the safest approach is: +# Whenever receiving an output image from an FSL command, explicitly search for the path +def findImage(name): + import os + from mrtrix3 import app + basename = basename.split('.')[0] + if os.path.isfile(basename + suffix()): + app.debug('Image at expected location: \"' + basename + suffix() + '\"') + for suf in ['.nii', '.nii.gz', '.img']: + if os.path.isfile(basename + suf): + app.debug('Expected image at \"' + basename + suffix() + '\", but found at \"' + basename + suf + '\"') + return basename + suf + app.error('Unable to find FSL output file for path \"' + name + '\"') + + + # For many FSL commands, the format of any output images will depend on the string # stored in 'FSLOUTPUTTYPE'. This may even override a filename extension provided # to the relevant command. Therefore use this function to 'guess' what the names @@ -34,18 +54,26 @@ def eddyBinary(cuda): def suffix(): import os from mrtrix3 import app + global _suffix + if _suffix: + return _suffix fsl_output_type = os.environ.get('FSLOUTPUTTYPE', '') if fsl_output_type == 'NIFTI': app.debug('NIFTI -> .nii') - return '.nii' - if fsl_output_type == 'NIFTI_GZ': + _suffix = '.nii' + elif fsl_output_type == 'NIFTI_GZ': app.debug('NIFTI_GZ -> .nii.gz') - return '.nii.gz' - if fsl_output_type == 'NIFTI_PAIR': + _suffix = '.nii.gz' + elif fsl_output_type == 'NIFTI_PAIR': app.debug('NIFTI_PAIR -> .img') - return '.img' - if fsl_output_type == 'NIFTI_PAIR_GZ': + _suffix = '.img' + elif fsl_output_type == 'NIFTI_PAIR_GZ': app.error('MRtrix3 does not support compressed NIFTI pairs; please change FSLOUTPUTTYPE environment variable') - app.warn('Environment variable FSLOUTPUTTYPE not set; FSL commands may fail, or script may fail to locate FSL command outputs') - return '.nii.gz' + elif fsl_output_type: + app.warn('Unrecognised value for environment variable FSLOUTPUTTYPE (\"' + fsl_output_type + '\"): Expecting compressed NIfTIs, but FSL commands may fail') + _suffix = '.nii.gz' + else: + app.warn('Environment variable FSLOUTPUTTYPE not set; FSL commands may fail, or script may fail to locate FSL command outputs') + _suffix = '.nii.gz' + return _suffix From 7512a56f2e982ebafdf9c57310d7a05988c1e239 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Thu, 3 Aug 2017 20:29:32 +1000 Subject: [PATCH 127/139] fsl.exeName(): Fix bug in initial implementation --- lib/mrtrix3/fsl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mrtrix3/fsl.py b/lib/mrtrix3/fsl.py index bc93e5aac3..3aae58a610 100644 --- a/lib/mrtrix3/fsl.py +++ b/lib/mrtrix3/fsl.py @@ -36,7 +36,7 @@ def eddyBinary(cuda): def findImage(name): import os from mrtrix3 import app - basename = basename.split('.')[0] + basename = name.split('.')[0] if os.path.isfile(basename + suffix()): app.debug('Image at expected location: \"' + basename + suffix() + '\"') for suf in ['.nii', '.nii.gz', '.img']: From 03d1d490732179c9c6b9fcb4f3190813a691254d Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Thu, 3 Aug 2017 16:10:28 +0100 Subject: [PATCH 128/139] mrdegibbs: add support for linking against FFTW This adds tests to the configure script to detect FFTW, and sets the requisite flags if found. It also required some changes to ensure all the FFTW plans are properly allocated prior to thread launch, since their plan allocation routines are not thread-safe. --- cmd/mrdegibbs.cpp | 46 ++++++++++++++++++++++++++++++++++++++-------- configure | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 8 deletions(-) diff --git a/cmd/mrdegibbs.cpp b/cmd/mrdegibbs.cpp index d5aaeff4d3..ef9f98048e 100644 --- a/cmd/mrdegibbs.cpp +++ b/cmd/mrdegibbs.cpp @@ -77,15 +77,32 @@ class ComputeSlice nsh (nsh), minW (minW), maxW (maxW), - X (slice_axes[0]), - Y (slice_axes[1]), in (in), out (out), - im1 (in.size(X), in.size(Y)), - im2 (im1.rows(), im1.cols()) { } + im1 (in.size(slice_axes[0]), in.size(slice_axes[1])), + im2 (im1.rows(), im1.cols()) { + prealloc_FFT(); + } + + ComputeSlice (const ComputeSlice& other) : + outer_axes (other.outer_axes), + slice_axes (other.slice_axes), + nsh (other.nsh), + minW (other.minW), + maxW (other.maxW), + in (other.in), + out (other.out), + fft (), + im1 (in.size(slice_axes[0]), in.size(slice_axes[1])), + im2 (im1.rows(), im1.cols()) { + prealloc_FFT(); + } + void operator() (const Iterator& pos) { + const int X = slice_axes[0]; + const int Y = slice_axes[1]; assign_pos_of (pos, outer_axes).to (in, out); for (auto l = Loop (slice_axes) (in); l; ++l) @@ -100,15 +117,28 @@ class ComputeSlice private: const vector& outer_axes; const vector& slice_axes; - const int nsh, minW, maxW, X, Y; + const int nsh, minW, maxW; Image in, out; - Eigen::FFT fftx, ffty; + Eigen::FFT fft; Eigen::MatrixXcd im1, im2, shifted; Eigen::VectorXcd v; - template FORCE_INLINE void FFT (Eigen::MatrixBase&& vec) { fftx.fwd (v, vec); vec = v; } + void prealloc_FFT () { + // needed to avoid within-thread allocations, + // which aren't thread-safe in FFTW: +#ifdef EIGEN_FFTW_DEFAULT + Eigen::VectorXcd tmp (im1.rows()); + FFT (tmp); + iFFT (tmp); + tmp.resize (im1.cols()); + FFT (tmp); + iFFT (tmp); +#endif + } + + template FORCE_INLINE void FFT (Eigen::MatrixBase&& vec) { fft.fwd (v, vec); vec = v; } template FORCE_INLINE void FFT (Eigen::MatrixBase& vec) { FFT (std::move (vec)); } - template FORCE_INLINE void iFFT (Eigen::MatrixBase&& vec) { fftx.inv (v, vec); vec = v; } + template FORCE_INLINE void iFFT (Eigen::MatrixBase&& vec) { fft.inv (v, vec); vec = v; } template FORCE_INLINE void iFFT (Eigen::MatrixBase& vec) { iFFT (std::move (vec)); } template FORCE_INLINE void row_FFT (Eigen::MatrixBase& mat) { for (auto n = 0; n < mat.rows(); ++n) FFT (mat.row(n)); } template FORCE_INLINE void row_iFFT (Eigen::MatrixBase& mat) { for (auto n = 0; n < mat.rows(); ++n) iFFT (mat.row(n)); } diff --git a/configure b/configure index cb41bae57c..d073714d2f 100755 --- a/configure +++ b/configure @@ -118,6 +118,12 @@ ENVIRONMENT VARIABLES TIFF_LDFLAGS Any flags required to link with the TIFF library. + FFTW_CFLAGS + Any flags required to compile with the FFTW library. + + FFTW_LDFLAGS + Any flags required to link with the FFTW library. + QMAKE The command to invoke Qt's qmake (default: qmake). @@ -253,6 +259,9 @@ zlib_ldflags = [ '-lz' ] tiff_cflags = [] tiff_ldflags = [ '-ltiff' ] +fftw_cflags = [] +fftw_ldflags = [ '-lfftw3' ] + class TempFile: def __init__ (self, suffix): @@ -833,6 +842,32 @@ if not openmp: +# FFTW: + +report ('Checking for FFTW library: ') + +if 'FFTW_CFLAGS' in os.environ.keys(): fftw_cflags = shlex.split (os.environ['FFTW_CFLAGS']) +if 'FFTW_LDFLAGS' in os.environ.keys(): fftw_ldflags = shlex.split (os.environ['FFTW_LDFLAGS']) + +try: + fftw_version = compile (''' +#include +#include + +int main() { + std::cout << fftw_version << "\\n"; + return (0); +} +''', cpp_flags + fftw_cflags, ld_flags + fftw_ldflags) + report (fftw_version.splitlines()[0] + '\n') + cpp_flags += [ '-DEIGEN_FFTW_DEFAULT' ] + fftw_cflags + ld_flags += fftw_ldflags + ld_lib_flags += fftw_ldflags +except: + report ('not found - FFTW support disabled\n'); + + + # add openmp flags if required and available From 647b24a1998761e0909ebdc7ac7b451ac6401ed1 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Thu, 3 Aug 2017 16:29:46 +0100 Subject: [PATCH 129/139] mrdegibbs: add test --- testing/data | 2 +- testing/tests/mrdegibbs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 testing/tests/mrdegibbs diff --git a/testing/data b/testing/data index b20e9f7657..e53aa18d52 160000 --- a/testing/data +++ b/testing/data @@ -1 +1 @@ -Subproject commit b20e9f765717f9afe8ca72937cbfe40048f7c9e4 +Subproject commit e53aa18d521a3d821d72b372e1ed55c9f1310bed diff --git a/testing/tests/mrdegibbs b/testing/tests/mrdegibbs new file mode 100644 index 0000000000..ea0b4b0446 --- /dev/null +++ b/testing/tests/mrdegibbs @@ -0,0 +1 @@ +mrdegibbs dwi.mif - | testing_diff_image - mrdegibbs/dwi.mif -voxel 1e-4 From af82f99a891301d3c522570a8084108c293560de Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Thu, 3 Aug 2017 16:48:06 +0100 Subject: [PATCH 130/139] additional INFO output when opening/creating images --- core/header.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/core/header.cpp b/core/header.cpp index 2d887a2301..c1efbdcdc1 100644 --- a/core/header.cpp +++ b/core/header.cpp @@ -12,6 +12,7 @@ */ +#include "mrtrix.h" #include "header.h" #include "phase_encoding.h" #include "stride.h" @@ -66,6 +67,20 @@ namespace MR } + namespace { + + std::string short_description (const Header& H) + { + vector dims; + for (size_t n = 0; n < H.ndim(); ++n) + dims.push_back (str(H.size(n))); + vector vox; + for (size_t n = 0; n < H.ndim(); ++n) + vox.push_back (str(H.spacing(n))); + + return " with dimensions " + join (dims, "x") + ", voxel spacing " + join (vox, "x") + ", datatype " + H.datatype().specifier(); + } + } @@ -128,12 +143,13 @@ namespace MR H.sanitise(); if (!do_not_realign_transform) H.realign_transform(); - } catch (Exception& E) { throw Exception (E, "error opening image \"" + image_name + "\""); } + INFO ("image \"" + H.name() + "\" opened" + short_description (H)); + return H; } @@ -291,6 +307,8 @@ namespace MR WARN (std::string ("requested datatype (") + previous_datatype.specifier() + ") not supported - substituting with " + H.datatype().specifier()); } + INFO ("image \"" + H.name() + "\" created" + short_description (H)); + return H; } From 3aefda440082ebf9a8ee6f6c9a7711221a7047c7 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Thu, 3 Aug 2017 16:51:05 +0100 Subject: [PATCH 131/139] dwidenoise: check image is 4-dimensional --- cmd/dwidenoise.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/dwidenoise.cpp b/cmd/dwidenoise.cpp index db1694420b..c18fd50ee5 100644 --- a/cmd/dwidenoise.cpp +++ b/cmd/dwidenoise.cpp @@ -197,6 +197,9 @@ void run () { auto dwi_in = Image::open (argument[0]).with_direct_io(3); + if (dwi_in.ndim() != 4 || dwi_in.size(3) <= 1) + throw Exception ("input image must be 4-dimensional"); + Image mask; auto opt = get_options ("mask"); if (opt.size()) { From 3c1ac612944bae48085fac58e4563fa8d1ded4ba Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 4 Aug 2017 14:26:39 +1000 Subject: [PATCH 132/139] fsl.findImage(): Fix glitches from testing --- bin/dwipreproc | 6 +++++- lib/mrtrix3/_5ttgen/fsl.py | 7 ++----- lib/mrtrix3/fsl.py | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/bin/dwipreproc b/bin/dwipreproc index af5461209f..88c978589c 100755 --- a/bin/dwipreproc +++ b/bin/dwipreproc @@ -481,7 +481,11 @@ if do_topup: index += 1 # Use the initial corrected volumes to derive a brain mask for eddy - run.command('mrcat ' + ' '.join(applytopup_image_list) + ' - | dwi2mask - - | maskfilter - dilate - | mrconvert - mask.nii -datatype float32 -stride -1,+2,+3') + if len(applytopup_image_list) == 1: + run.command('dwi2mask ' + applytopup_image_list[0] + ' - | maskfilter - dilate - | mrconvert - mask.nii -datatype float32 -stride -1,+2,+3') + else: + run.command('mrcat ' + ' '.join(applytopup_image_list) + ' - | dwi2mask - - | maskfilter - dilate - | mrconvert - mask.nii -datatype float32 -stride -1,+2,+3') + for entry in applytopup_image_list: file.delTempFile(entry) diff --git a/lib/mrtrix3/_5ttgen/fsl.py b/lib/mrtrix3/_5ttgen/fsl.py index b5011185c1..709d343fa8 100644 --- a/lib/mrtrix3/_5ttgen/fsl.py +++ b/lib/mrtrix3/_5ttgen/fsl.py @@ -135,13 +135,10 @@ def execute(): run.command(ssroi_cmd + ' T1.nii T1_preBET' + fsl_suffix + ' -maskMASK mni_mask.nii' + ssroi_roi_option, False) else: run.command(ssroi_cmd + ' T1.nii T1_preBET' + fsl_suffix + ' -b', False) - - # For whatever reason, the output file from standard_space_roi may not be - # completed before BET is run - file.waitFor('T1_preBET' + fsl_suffix) + pre_bet_image = fsl.findImage('T1_preBET') # BET - run.command(bet_cmd + ' T1_preBET' + fsl_suffix + ' T1_BET' + fsl_suffix + ' -f 0.15 -R') + run.command(bet_cmd + ' ' + pre_bet_image + ' T1_BET' + fsl_suffix + ' -f 0.15 -R') fast_t1_input = fsl.findImage('T1_BET' + fsl_suffix) if os.path.exists('T2.nii'): diff --git a/lib/mrtrix3/fsl.py b/lib/mrtrix3/fsl.py index 3aae58a610..c9871b9f1b 100644 --- a/lib/mrtrix3/fsl.py +++ b/lib/mrtrix3/fsl.py @@ -39,6 +39,7 @@ def findImage(name): basename = name.split('.')[0] if os.path.isfile(basename + suffix()): app.debug('Image at expected location: \"' + basename + suffix() + '\"') + return basename + suffix() for suf in ['.nii', '.nii.gz', '.img']: if os.path.isfile(basename + suf): app.debug('Expected image at \"' + basename + suffix() + '\", but found at \"' + basename + suf + '\"') From 0bb663c532c4102fc6b56d51e3b22ff49311304b Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Fri, 4 Aug 2017 14:30:42 +1000 Subject: [PATCH 133/139] 5ttgen fsl: Find FIRST output despite FSLOUTPUTTYPE non-conformity Unfortunately only works in the absence of SGE; to fix this issue with SGE would require modifying the file.waitFor() function to accept a list of paths, and repeately check to see if _any_ of those paths exist. --- lib/mrtrix3/_5ttgen/fsl.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/mrtrix3/_5ttgen/fsl.py b/lib/mrtrix3/_5ttgen/fsl.py index 709d343fa8..d82a684674 100644 --- a/lib/mrtrix3/_5ttgen/fsl.py +++ b/lib/mrtrix3/_5ttgen/fsl.py @@ -174,7 +174,10 @@ def execute(): app.console('(note however that FIRST may fail silently, and hence this script may hang indefinitely)') file.waitFor(combined_image_path) else: - app.error('FSL FIRST has failed; not all structures were segmented successfully (check ' + path.toTemp('first.logs', False) + ')') + combined_image_path = fsl.findImage('first_all_none_firstseg') + if not os.path.isfile(combined_image_path): + app.error('FSL FIRST has failed; not all structures were segmented successfully (check ' + +path.toTemp('first.logs', False) + ')') # Convert FIRST meshes to partial volume images pve_image_list = [ ] From c97dda885e605e1b9b67a31455b0933fdc6968c5 Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Fri, 4 Aug 2017 10:40:31 +0100 Subject: [PATCH 134/139] mrdegibbs: additional documentation about partial Fourier and motion correction --- cmd/mrdegibbs.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/cmd/mrdegibbs.cpp b/cmd/mrdegibbs.cpp index ef9f98048e..398fddb79d 100644 --- a/cmd/mrdegibbs.cpp +++ b/cmd/mrdegibbs.cpp @@ -26,11 +26,23 @@ void usage () { AUTHOR = "Ben Jeurissen (ben.jeurissen@uantwerpen.be) & J-Donald Tournier (jdtournier@gmail.com)"; - SYNOPSIS = "Remove Gibbs Ringing Artifact"; + SYNOPSIS = "Remove Gibbs Ringing Artifacts"; DESCRIPTION + "This application attempts to remove Gibbs ringing artefacts from MRI images using the method " - "of local subvoxel-shifts proposed by Kellner et al. (see reference below for details)."; + "of local subvoxel-shifts proposed by Kellner et al. (see reference below for details)." + + + "This command is designed to run on data directly after it has been reconstructed by the scanner, " + "before any interpolation of any kind has taken place. You should not run this command after any " + "form of motion correction (e.g. not after dwipreproc). Similarly, if you intend running dwidenoise, " + "you should run this command afterwards, since it has the potential to alter the noise structure, " + "which would impact on dwidenoise's performance." + + + "Note that this method is designed to work on images acquired with full k-space coverage. " + "Running this method on partial Fourier ('half-scan') data may lead to suboptimal and/or biased " + "results, as noted in the original reference below. There is currently no means of dealing with this; " + "users should exercise caution when using this method on partial Fourier data, and inspect its output " + "for any obvious artefacts. "; ARGUMENTS From 9f0ec9a75a8605c3224112729d290e406ae4fe2e Mon Sep 17 00:00:00 2001 From: J-Donald Tournier Date: Fri, 4 Aug 2017 11:04:03 +0100 Subject: [PATCH 135/139] mrdegibbs: update testing data now using a full size b=0 image, and comparing against output of original 'unring' implementation. --- testing/data | 2 +- testing/tests/mrdegibbs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/data b/testing/data index e53aa18d52..72e0a40550 160000 --- a/testing/data +++ b/testing/data @@ -1 +1 @@ -Subproject commit e53aa18d521a3d821d72b372e1ed55c9f1310bed +Subproject commit 72e0a4055092a9f180fd77a7f6096df90b7622c0 diff --git a/testing/tests/mrdegibbs b/testing/tests/mrdegibbs index ea0b4b0446..fb102b4f00 100644 --- a/testing/tests/mrdegibbs +++ b/testing/tests/mrdegibbs @@ -1 +1 @@ -mrdegibbs dwi.mif - | testing_diff_image - mrdegibbs/dwi.mif -voxel 1e-4 +mrdegibbs b0.nii.gz - | testing_diff_image - mrdegibbs/b0_unring.nii.gz -abs 0.2 From 959ed4e5a4cd2ef5da72dc739043f1aa1211c815 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Mon, 7 Aug 2017 10:50:08 +1000 Subject: [PATCH 136/139] userdocs update --- docs/reference/commands/mrview.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/reference/commands/mrview.rst b/docs/reference/commands/mrview.rst index 605b4d0bcb..542e303ed0 100644 --- a/docs/reference/commands/mrview.rst +++ b/docs/reference/commands/mrview.rst @@ -126,6 +126,14 @@ Tractography tool options - **-tractography.opacity value** Opacity of tractography display, [0.0, 1.0], default is 1.0. +- **-tractography.slab value** Slab thickness of tractography display, in mm. -1 to turn off crop to slab. + +- **-tractography.tsf_load tsf** Load the specified tractography scalar file. + +- **-tractography.tsf_range range** Set range for the tractography scalar file. Requires tractography.tsf_load already provided. RangeMin,RangeMax + +- **-tractography.tsf_thresh thresh** Set thresholds for the tractography scalar file. Requires tractography.tsf_load already provided. ThresholdMin,ThesholdMax + ODF tool options ^^^^^^^^^^^^^^^^ From 40e01bc7e3866f83459bc67a2d10c949f435126d Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Mon, 7 Aug 2017 11:17:10 +1000 Subject: [PATCH 137/139] userdocs update --- docs/reference/commands/mrdegibbs.rst | 88 +++++++++++++++++++++++++++ docs/reference/commands_list.rst | 2 + 2 files changed, 90 insertions(+) create mode 100644 docs/reference/commands/mrdegibbs.rst diff --git a/docs/reference/commands/mrdegibbs.rst b/docs/reference/commands/mrdegibbs.rst new file mode 100644 index 0000000000..7a2c033cfc --- /dev/null +++ b/docs/reference/commands/mrdegibbs.rst @@ -0,0 +1,88 @@ +.. _mrdegibbs: + +mrdegibbs +=================== + +Synopsis +-------- + +Remove Gibbs Ringing Artifacts + +Usage +-------- + +:: + + mrdegibbs [ options ] in out + +- *in*: the input image. +- *out*: the output image. + +Description +----------- + +This application attempts to remove Gibbs ringing artefacts from MRI images using the method of local subvoxel-shifts proposed by Kellner et al. (see reference below for details). + +This command is designed to run on data directly after it has been reconstructed by the scanner, before any interpolation of any kind has taken place. You should not run this command after any form of motion correction (e.g. not after dwipreproc). Similarly, if you intend running dwidenoise, you should run this command afterwards, since it has the potential to alter the noise structure, which would impact on dwidenoise's performance. + +Note that this method is designed to work on images acquired with full k-space coverage. Running this method on partial Fourier ('half-scan') data may lead to suboptimal and/or biased results, as noted in the original reference below. There is currently no means of dealing with this; users should exercise caution when using this method on partial Fourier data, and inspect its output for any obvious artefacts. + +Options +------- + +- **-axes list** select the slice axes (default: 0,1 - i.e. x-y). + +- **-nshifts value** discretization of subpixel spacing (default: 20). + +- **-minW value** left border of window used for TV computation (default: 1). + +- **-maxW value** right border of window used for TV computation (default: 3). + +Data type options +^^^^^^^^^^^^^^^^^ + +- **-datatype spec** specify output image data type. Valid choices are: float32, float32le, float32be, float64, float64le, float64be, int64, uint64, int64le, uint64le, int64be, uint64be, int32, uint32, int32le, uint32le, int32be, uint32be, int16, uint16, int16le, uint16le, int16be, uint16be, cfloat32, cfloat32le, cfloat32be, cfloat64, cfloat64le, cfloat64be, int8, uint8, bit. + +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 +^^^^^^^^^^ + +Kellner, E; Dhital, B; Kiselev, V.G & Reisert, M. Gibbs-ringing artifact removal based on local subvoxel-shifts. Magnetic Resonance in Medicine, 2016, 76, 1574–1581. + +-------------- + + + +**Author:** Ben Jeurissen (ben.jeurissen@uantwerpen.be) & J-Donald Tournier (jdtournier@gmail.com) + +**Copyright:** Copyright (c) 2008-2017 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 http://www.mrtrix.org/. + + diff --git a/docs/reference/commands_list.rst b/docs/reference/commands_list.rst index 334465ea3a..d20aefb4dc 100644 --- a/docs/reference/commands_list.rst +++ b/docs/reference/commands_list.rst @@ -61,6 +61,7 @@ List of MRtrix3 commands commands/mrclusterstats commands/mrconvert commands/mrcrop + commands/mrdegibbs commands/mrdump commands/mredit commands/mrfilter @@ -171,6 +172,7 @@ List of MRtrix3 commands :ref:`mrclusterstats`, "Voxel-based analysis using permutation testing and threshold-free cluster enhancement" :ref:`mrconvert`, "Perform conversion between different file types and optionally extract a subset of the input image" :ref:`mrcrop`, "Crop an image to a reduced field of view" + :ref:`mrdegibbs`, "Remove Gibbs Ringing Artifacts" :ref:`mrdump`, "Print out the values within an image" :ref:`mredit`, "Directly edit the intensities within an image from the command-line" :ref:`mrfilter`, "Perform filtering operations on 3D / 4D MR images" From 00ad864857e4be10b1ed79d99516657e12800837 Mon Sep 17 00:00:00 2001 From: Thijs Dhollander Date: Mon, 7 Aug 2017 12:16:52 +1000 Subject: [PATCH 138/139] copyright updates --- cmd/mrdegibbs.cpp | 1 + src/gui/mrview/tool/tractography/tractogram_enums.h | 13 ++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/mrdegibbs.cpp b/cmd/mrdegibbs.cpp index 398fddb79d..17a9d9c880 100644 --- a/cmd/mrdegibbs.cpp +++ b/cmd/mrdegibbs.cpp @@ -11,6 +11,7 @@ * For more details, see http://www.mrtrix.org/. */ + #include #include "command.h" diff --git a/src/gui/mrview/tool/tractography/tractogram_enums.h b/src/gui/mrview/tool/tractography/tractogram_enums.h index 9149ef4749..8de9c312dd 100644 --- a/src/gui/mrview/tool/tractography/tractogram_enums.h +++ b/src/gui/mrview/tool/tractography/tractogram_enums.h @@ -1,18 +1,17 @@ -/* - * Copyright (c) 2008-2016 the MRtrix3 contributors +/* Copyright (c) 2008-2017 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/ + * 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 + * but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * For more details, see http://www.mrtrix.org/. */ + #ifndef __gui_mrview_tool_tractogram_enums_h__ #define __gui_mrview_tool_tractogram_enums_h__ From 9a1de6f5855145c98e259dac307bdb02f36cd0f1 Mon Sep 17 00:00:00 2001 From: Ben Jeurissen Date: Tue, 8 Aug 2017 02:39:16 +0200 Subject: [PATCH 139/139] Handle shell selection when using msmt_csd This should address issue #1081 --- src/dwi/sdeconv/msmt_csd.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dwi/sdeconv/msmt_csd.h b/src/dwi/sdeconv/msmt_csd.h index df4e68d239..ed3dadb55c 100644 --- a/src/dwi/sdeconv/msmt_csd.h +++ b/src/dwi/sdeconv/msmt_csd.h @@ -45,7 +45,7 @@ namespace MR Shared (const Header& dwi_header) : grad (DWI::get_valid_DW_scheme (dwi_header)), shells (grad), - HR_dirs (DWI::Directions::electrostatic_repulsion_300()) { } + HR_dirs (DWI::Directions::electrostatic_repulsion_300()) { shells.select_shells(false,false,false); } void parse_cmdline_options() @@ -209,7 +209,7 @@ namespace MR const Eigen::MatrixXd grad; - const DWI::Shells shells; + DWI::Shells shells; Eigen::MatrixXd HR_dirs; vector lmax, lmax_response; vector responses;