Developer documentation
Version 3.0.3-105-gd3941f44
csd.h
Go to the documentation of this file.
1/* Copyright (c) 2008-2022 the MRtrix3 contributors.
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 *
7 * Covered Software is provided under this License on an "as is"
8 * basis, without warranty of any kind, either expressed, implied, or
9 * statutory, including, without limitation, warranties that the
10 * Covered Software is free of defects, merchantable, fit for a
11 * particular purpose or non-infringing.
12 * See the Mozilla Public License v. 2.0 for more details.
13 *
14 * For more details, see http://www.mrtrix.org/.
15 */
16
17#ifndef __dwi_sdeconv_csd_h__
18#define __dwi_sdeconv_csd_h__
19
20#include "app.h"
21#include "header.h"
22#include "dwi/gradient.h"
23#include "math/SH.h"
24#include "math/ZSH.h"
26#include "math/least_squares.h"
27
28#define NORM_LAMBDA_MULTIPLIER 0.0002
29
30#define DEFAULT_CSD_LMAX 8
31#define DEFAULT_CSD_NEG_LAMBDA 1.0
32#define DEFAULT_CSD_NORM_LAMBDA 1.0
33#define DEFAULT_CSD_THRESHOLD 0.0
34#define DEFAULT_CSD_NITER 50
35
36namespace MR
37{
38 namespace DWI
39 {
40 namespace SDeconv
41 {
42
43 extern const App::OptionGroup CSD_options;
44
45 class CSD { MEMALIGN(CSD)
46 public:
47
48 class Shared { MEMALIGN(Shared)
49 public:
50
51 Shared (const Header& dwi_header) :
53 neg_lambda (DEFAULT_CSD_NEG_LAMBDA),
54 norm_lambda (DEFAULT_CSD_NORM_LAMBDA),
55 threshold (DEFAULT_CSD_THRESHOLD),
56 lmax_response (0),
57 lmax_cmdline (0),
58 lmax (0),
59 niter (DEFAULT_CSD_NITER) {
60 grad = DWI::get_DW_scheme (dwi_header);
61 // Discard b=0 (b=0 normalisation not supported in this version)
62 // Only allow selection of one non-zero shell from command line
63 dwis = DWI::Shells (grad).select_shells (true, false, true).largest().get_volumes();
64 DW_dirs = DWI::gen_direction_matrix (grad, dwis);
65
66 lmax_data = Math::SH::LforN (dwis.size());
67 }
68
69
70 void parse_cmdline_options()
71 {
72 using namespace App;
73 auto opt = get_options ("lmax");
74 if (opt.size()) {
75 auto list = parse_ints<uint32_t> (opt[0][0]);
76 if (list.size() != 1)
77 throw Exception ("CSD algorithm expects a single lmax to be specified");
78 lmax_cmdline = list.front();
79 }
80 opt = get_options ("filter");
81 if (opt.size())
82 init_filter = load_vector (opt[0][0]);
83 opt = get_options ("directions");
84 if (opt.size())
85 HR_dirs = load_matrix (opt[0][0]);
86 opt = get_options ("neg_lambda");
87 if (opt.size())
88 neg_lambda = opt[0][0];
89 opt = get_options ("norm_lambda");
90 if (opt.size())
91 norm_lambda = opt[0][0];
92 opt = get_options ("threshold");
93 if (opt.size())
94 threshold = opt[0][0];
95 opt = get_options ("niter");
96 if (opt.size())
97 niter = opt[0][0];
98 }
99
100
101 void set_response (const std::string& path)
102 {
103 INFO ("loading response function from file \"" + path + "\"");
104 set_response (load_vector (path));
105 }
106
107 template <class Derived>
108 void set_response (const Eigen::MatrixBase<Derived>& in)
109 {
110 response = in;
111 lmax_response = Math::ZSH::LforN (response.size());
112 INFO ("setting response function using even SH coefficients: " + str (response.transpose()));
113 }
114
115
116 void init ()
117 {
118 using namespace Math::SH;
119
120 if (lmax_data <= 0)
121 throw Exception ("data contain too few directions even for lmax = 2");
122
123 if (lmax_response <= 0)
124 throw Exception ("response function does not contain anisotropic terms");
125
126 lmax = ( lmax_cmdline ? lmax_cmdline : std::min (lmax_response, uint32_t(DEFAULT_CSD_LMAX)) );
127
128 if (lmax <= 0 || lmax % 2)
129 throw Exception ("lmax must be a positive even integer");
130
131 assert (response.size());
132 lmax_response = std::min (lmax_response, std::min (lmax_data, lmax));
133 INFO ("calculating even spherical harmonic components up to order " + str (lmax_response) + " for initialisation");
134
135 if (!init_filter.size())
136 init_filter = Eigen::VectorXd::Ones(3);
137 init_filter.conservativeResizeLike (Eigen::VectorXd::Zero (Math::ZSH::NforL (lmax_response)));
138
139 auto RH = Math::ZSH::ZSH2RH (response);
140 if (size_t(RH.size()) < Math::ZSH::NforL (lmax))
141 RH.conservativeResizeLike (Eigen::VectorXd::Zero (Math::ZSH::NforL (lmax)));
142
143 // inverse sdeconv for initialisation:
144 auto fconv = init_transform (DW_dirs, lmax_response);
145 rconv.resize (fconv.cols(), fconv.rows());
146 fconv.diagonal().array() += 1.0e-2;
147 //fconv.save ("fconv.txt");
148 rconv = Math::pinv (fconv);
149 //rconv.save ("rconv.txt");
150 ssize_t l = 0, nl = 1;
151 for (ssize_t row = 0; row < rconv.rows(); ++row) {
152 if (row >= nl) {
153 l++;
154 nl = NforL (2*l);
155 }
156 rconv.row (row).array() *= init_filter[l] / RH[l];
157 }
158
159 // forward sconv for iteration, using all response function
160 // coefficients up to the requested lmax:
161 INFO ("calculating even spherical harmonic components up to order " + str (lmax) + " for output");
162 fconv = init_transform (DW_dirs, lmax);
163 l = 0;
164 nl = 1;
165 for (ssize_t col = 0; col < fconv.cols(); ++col) {
166 if (col >= nl) {
167 l++;
168 nl = NforL (2*l);
169 }
170 fconv.col (col).array() *= RH[l];
171 }
172
173 // high-res sampling to apply constraint:
174 HR_trans = init_transform (HR_dirs, lmax);
175 default_type constraint_multiplier = neg_lambda * 50.0 * response[0] / default_type (HR_trans.rows());
176 HR_trans.array() *= constraint_multiplier;
177
178 // adjust threshold accordingly:
179 threshold *= constraint_multiplier;
180
181 // precompute as much as possible ahead of Cholesky decomp:
182 assert (fconv.cols() <= HR_trans.cols());
183 M.resize (DW_dirs.rows(), HR_trans.cols());
184 M.leftCols (fconv.cols()) = fconv;
185 M.rightCols (M.cols() - fconv.cols()).setZero();
186 Mt_M.resize (M.cols(), M.cols());
187 Mt_M.triangularView<Eigen::Lower>() = M.transpose() * M;
188
189
190 // min-norm constraint:
191 if (norm_lambda) {
192 norm_lambda *= NORM_LAMBDA_MULTIPLIER * Mt_M (0,0);
193 Mt_M.diagonal().array() += norm_lambda;
194 }
195
196 INFO ("constrained spherical deconvolution initialised successfully");
197 }
198
199 size_t nSH () const {
200 return HR_trans.cols();
201 }
202
203 Eigen::MatrixXd grad;
204 Eigen::VectorXd response, init_filter;
205 Eigen::MatrixXd DW_dirs, HR_dirs;
206 Eigen::MatrixXd rconv, HR_trans, M, Mt_M;
207 default_type neg_lambda, norm_lambda, threshold;
208 vector<size_t> dwis;
209 uint32_t lmax_response, lmax_data, lmax_cmdline, lmax;
210 size_t niter;
211 };
212
213
214
215
216
217
218
219
220 CSD (const Shared& shared_data) :
221 shared (shared_data),
222 work (shared.Mt_M.rows(), shared.Mt_M.cols()),
223 HR_T (shared.HR_trans.rows(), shared.HR_trans.cols()),
224 F (shared.HR_trans.cols()),
225 init_F (shared.rconv.rows()),
226 HR_amps (shared.HR_trans.rows()),
227 Mt_b (shared.HR_trans.cols()),
228 llt (work.rows()),
229 old_neg (shared.HR_trans.rows()) { }
230
231 CSD (const CSD&) = default;
232
233 ~CSD() { }
234
235 template <class VectorType>
236 void set (const VectorType& DW_signals) {
237 F.head (shared.rconv.rows()) = shared.rconv * DW_signals;
238 F.tail (F.size()-shared.rconv.rows()).setZero();
239 old_neg.assign (1, -1);
240
241 Mt_b = shared.M.transpose() * DW_signals;
242 }
243
244 bool iterate() {
245 neg.clear();
246 HR_amps = shared.HR_trans * F;
247 for (ssize_t n = 0; n < HR_amps.size(); n++)
248 if (HR_amps[n] < shared.threshold)
249 neg.push_back (n);
250
251 if (old_neg == neg)
252 return true;
253
254 work.triangularView<Eigen::Lower>() = shared.Mt_M.triangularView<Eigen::Lower>();
255
256 if (neg.size()) {
257 for (size_t i = 0; i < neg.size(); i++)
258 HR_T.row (i) = shared.HR_trans.row (neg[i]);
259 auto HR_T_view = HR_T.topRows (neg.size());
260 work.triangularView<Eigen::Lower>() += HR_T_view.transpose() * HR_T_view;
261 }
262
263 F.noalias() = llt.compute (work.triangularView<Eigen::Lower>()).solve (Mt_b);
264
265 old_neg = neg;
266
267 return false;
268 }
269
270 const Eigen::VectorXd& FOD () const { return F; }
271
272
273 const Shared& shared;
274
275 protected:
276 Eigen::MatrixXd work, HR_T;
277 Eigen::VectorXd F, init_F, HR_amps, Mt_b;
278 Eigen::LLT<Eigen::MatrixXd> llt;
280 };
281
282
283 }
284 }
285}
286
287#endif
288
289
a class to hold a named list of Option's
Eigen::MatrixXd work
Definition: csd.h:276
Eigen::VectorXd Mt_b
Definition: csd.h:277
Eigen::VectorXd F
Definition: csd.h:277
Eigen::LLT< Eigen::MatrixXd > llt
Definition: csd.h:278
Eigen::VectorXd init_F
Definition: csd.h:277
vector< int > neg
Definition: csd.h:279
vector< int > old_neg
Definition: csd.h:279
Eigen::MatrixXd HR_T
Definition: csd.h:276
Eigen::VectorXd HR_amps
Definition: csd.h:277
const vector< size_t > & get_volumes() const
Definition: shells.h:82
const Shell & largest() const
Definition: shells.h:120
Shells & select_shells(const bool force_singleshell, const bool force_with_bzero, const bool force_without_bzero)
#define DEFAULT_CSD_NITER
Definition: csd.h:34
#define DEFAULT_CSD_THRESHOLD
Definition: csd.h:33
#define DEFAULT_CSD_LMAX
Definition: csd.h:30
#define NORM_LAMBDA_MULTIPLIER
Definition: csd.h:28
#define DEFAULT_CSD_NEG_LAMBDA
Definition: csd.h:31
#define DEFAULT_CSD_NORM_LAMBDA
Definition: csd.h:32
#define INFO(msg)
Definition: exception.h:74
Eigen::Matrix< typename MatrixType::Scalar, Eigen::Dynamic, Eigen::Dynamic > pinv(const MatrixType &M)
return Moore-Penrose pseudo-inverse of M
Definition: least_squares.h:39
size_t LforN(int N)
returns the largest lmax given N parameters
Definition: SH.h:70
size_t NforL(int lmax)
the number of (even-degree) coefficients for the given value of lmax
Definition: SH.h:46
Eigen::Matrix< typename MatrixType::Scalar, Eigen::Dynamic, Eigen::Dynamic > init_transform(const MatrixType &dirs, const int lmax)
form the SH->amplitudes matrix
Definition: SH.h:82
VectorType1 & ZSH2RH(VectorType1 &rh, const VectorType2 &zsh)
Definition: ZSH.h:221
size_t NforL(int lmax)
the number of (even-degree) coefficients for the given value of lmax
Definition: ZSH.h:42
size_t LforN(int N)
returns the largest lmax given N parameters
Definition: ZSH.h:54
const vector< ParsedOption > get_options(const std::string &name)
return all command-line options matching name
void init(int argc, const char *const *argv)
initialise MRtrix and parse command-line arguments
Eigen::MatrixXd electrostatic_repulsion_300()
Definition: predefined.h:51
const App::OptionGroup CSD_options
Eigen::MatrixXd get_DW_scheme(const Header &header, BValueScalingBehaviour bvalue_scaling=BValueScalingBehaviour::Auto)
get the fully-interpreted DW gradient encoding matrix
Eigen::MatrixXd gen_direction_matrix(const MatrixType &grad, const IndexVectorType &dwi)
convert the DW encoding matrix in grad into a azimuth/elevation direction set, using only the DWI vol...
Definition: gradient.h:75
Definition: base.h:24
Eigen::Matrix< ValueType, Eigen::Dynamic, Eigen::Dynamic > load_matrix(const std::string &filename)
read matrix data into an Eigen::Matrix filename
Definition: math.h:196
double default_type
the default type used throughout MRtrix
Definition: types.h:228
std::string str(const T &value, int precision=0)
Definition: mrtrix.h:247
Eigen::Matrix< ValueType, Eigen::Dynamic, 1 > load_vector(const std::string &filename)
read the vector data from filename
Definition: math.h:327
#define MEMALIGN(...)
Definition: types.h:185