Developer documentation
Version 3.0.3-105-gd3941f44
reorient.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 __registration_transform_reorient_h__
18#define __registration_transform_reorient_h__
19
20#include "algo/threaded_loop.h"
21#include "math/SH.h"
22#include "math/least_squares.h"
23#include "adapter/jacobian.h"
25
26namespace MR
27{
28 namespace Registration
29 {
30 namespace Transform
31 {
32
33
34 FORCE_INLINE Eigen::MatrixXd aPSF_weights_to_FOD_transform (const int num_SH, const Eigen::MatrixXd& directions)
35 {
36 const size_t lmax = Math::SH::LforN (num_SH);
37 Eigen::MatrixXd delta_matrix = Math::SH::init_transform_cart(directions.transpose(), lmax);
39 Math::SH::sconv_mat(delta_matrix, aPSF.RH_coefs());
40 return delta_matrix.transpose();
41 }
42
44 {
45 max_n_SH = 0;
46 vector<vector<ssize_t>> start_nvols;
47 if (mcsettings.size() != 0) {
48 for (const auto & mc : mcsettings) {
49 if (mc.do_reorientation && mc.lmax > 0) {
50 start_nvols.emplace_back(std::initializer_list<ssize_t>{(ssize_t) mc.start, (ssize_t) mc.nvols});
51 max_n_SH = std::max (mc.nvols, max_n_SH);
52 }
53 }
54 }
55 assert (max_n_SH > 1);
56 assert (start_nvols.size());
57 return start_nvols;
58 }
59
60 template <class FODImageType>
62
63 public:
64 LinearKernelMultiContrast (ssize_t n_vol,
65 ssize_t max_n_SH,
66 const transform_type& linear_transform,
67 const Eigen::MatrixXd& directions,
68 const vector<vector<ssize_t>>& vstart_nvols,
69 const bool modulate) : fod (n_vol), max_n_SH (max_n_SH), start_nvols (vstart_nvols)
70 {
71 assert (n_vol > max_n_SH);
72 assert (start_nvols.size());
73 Eigen::MatrixXd transformed_directions = linear_transform.linear().inverse() * directions;
74 // precompute projection for maximum requested lmax
75 if (modulate) {
76 Eigen::VectorXd modulation_factors = transformed_directions.colwise().norm() / linear_transform.linear().inverse().determinant();
77 transformed_directions.colwise().normalize();
78 transform.noalias() = aPSF_weights_to_FOD_transform (max_n_SH, transformed_directions) * modulation_factors.asDiagonal()
80 } else {
81 transformed_directions.colwise().normalize();
82 transform.noalias() = aPSF_weights_to_FOD_transform (max_n_SH, transformed_directions)
84 }
85 }
86
87 void operator() (FODImageType& in, FODImageType& out) {
88 in.index(3) = 0; // TODO do we need this?
89 // TODO is it faster to check whether any voxel contains an FOD before copying the row?
90 fod = in.row(3);
91 for (auto const & sn : start_nvols) {
92 if (fod[sn[0]] > 0.0) { // only reorient voxels that contain an FOD
93 fod.segment(sn[0],sn[1]) = transform.block(0,0,sn[1],sn[1]) * fod.segment(sn[0],sn[1]);
94 }
95 }
96 in.index(3) = 0; // TODO do we need this?
97 out.row(3) = fod;
98 }
99
100 protected:
101 Eigen::VectorXd fod;
102 ssize_t max_n_SH;
104 Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> transform;
105 };
106
107
108
109 template <class FODImageType>
111
112 public:
113 LinearKernel (const ssize_t n_SH,
114 const transform_type& linear_transform,
115 const Eigen::MatrixXd& directions,
116 const bool modulate) : fod (n_SH)
117 {
118 Eigen::MatrixXd transformed_directions = linear_transform.linear().inverse() * directions;
119
120 if (modulate) {
121 Eigen::VectorXd modulation_factors = transformed_directions.colwise().norm() / linear_transform.linear().inverse().determinant();
122 transformed_directions.colwise().normalize();
123 transform.noalias() = aPSF_weights_to_FOD_transform (n_SH, transformed_directions) * modulation_factors.asDiagonal()
124 * Math::pinv (aPSF_weights_to_FOD_transform (n_SH, directions));
125 } else {
126 transformed_directions.colwise().normalize();
127 transform.noalias() = aPSF_weights_to_FOD_transform (n_SH, transformed_directions)
128 * Math::pinv (aPSF_weights_to_FOD_transform (n_SH, directions));
129 }
130 }
131
132 void operator() (FODImageType& in, FODImageType& out)
133 {
134 in.index(3) = 0;
135 if (in.value() > 0.0) { // only reorient voxels that contain an FOD
136 fod = in.row(3);
137 fod = transform * fod;
138 out.row(3) = fod;
139 }
140 }
141
142 protected:
143 Eigen::VectorXd fod;
144 Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> transform;
145 };
146
147
148 /*
149 * reorient all FODs in an image with a linear transform.
150 * Note the input image can be the same as the output image
151 */
152 template <class FODImageType>
153 void reorient (FODImageType& input_fod_image,
154 FODImageType& output_fod_image,
155 const transform_type& transform,
156 const Eigen::MatrixXd& directions,
157 bool modulate = false,
159 {
160 assert (directions.cols() > directions.rows());
161 vector<vector<ssize_t>> start_nvols;
162 size_t max_n_SH (0);
163 if (multi_contrast_settings.size())
164 start_nvols = multiContrastSetting2start_nvols (multi_contrast_settings, max_n_SH);
165
166 if (start_nvols.size()) {
167 assert (max_n_SH > 1);
168 ThreadedLoop (input_fod_image, 0, 3)
169 .run (LinearKernelMultiContrast<FODImageType>(input_fod_image.size(3), max_n_SH, transform, directions, start_nvols, modulate),
170 input_fod_image, output_fod_image);
171 } else {
172 ThreadedLoop (input_fod_image, 0, 3)
173 .run (LinearKernel<FODImageType>(input_fod_image.size(3), transform, directions, modulate),
174 input_fod_image, output_fod_image);
175 }
176 }
177
178
179 /*
180 * reorient all FODs in an image with a linear transform.
181 * Note the input image can be the same as the output image
182 */
183 template <class FODImageType>
184 void reorient (const std::string progress_message,
185 FODImageType& input_fod_image,
186 FODImageType& output_fod_image,
187 const transform_type& transform,
188 const Eigen::MatrixXd& directions,
189 bool modulate = false,
191 {
192 assert (directions.cols() > directions.rows());
193 vector<vector<ssize_t>> start_nvols;
194 size_t max_n_SH (0);
195 if (multi_contrast_settings.size())
196 start_nvols = multiContrastSetting2start_nvols (multi_contrast_settings, max_n_SH);
197
198 if (start_nvols.size()) {
199 assert (max_n_SH > 1);
200 ThreadedLoop (progress_message, input_fod_image, 0, 3)
201 .run (LinearKernelMultiContrast<FODImageType>(input_fod_image.size(3), max_n_SH, transform, directions, start_nvols, modulate),
202 input_fod_image, output_fod_image);
203 } else {
204 ThreadedLoop (progress_message, input_fod_image, 0, 3)
205 .run (LinearKernel<FODImageType>(input_fod_image.size(3), transform, directions, modulate),
206 input_fod_image, output_fod_image);
207 }
208 }
209
210 template <class FODImageType>
212
213 public:
214 NonLinearKernelMultiContrast (ssize_t n_vol,
215 ssize_t max_n_SH,
217 const Eigen::MatrixXd& directions,
218 const vector<vector<ssize_t>>& vstart_nvols,
219 const bool modulate) :
221 modulate (modulate), start_nvols (vstart_nvols), fod (n_vol)
222 {
223 for (auto const & sn : start_nvols)
225 assert (n_vol > 0);
226 assert (start_nvols.size());
227 }
228
229 void operator() (FODImageType& image) {
230 // get highest n_SH for compartments that contain a non-zero FOD in this voxel
231 ssize_t max_n_SHvox = 0;
232 for (auto const & sn : start_nvols) {
233 image.index(3) = sn[0];
234 if (image.value() > 0.0) {
235 max_n_SHvox = std::max (max_n_SHvox, sn[1]);
236 }
237 }
238 if (max_n_SHvox == 0)
239 return;
240
241 image.index(3) = 0;
242 fod = image.row(3);
243
244 for (size_t dim = 0; dim < 3; ++dim)
245 jacobian_adapter.index(dim) = image.index(dim);
246 Eigen::MatrixXd jacobian = jacobian_adapter.value().inverse().template cast<default_type>();
247 Eigen::MatrixXd transformed_directions = jacobian * directions;
248
249 if (modulate)
250 modulation_factors = transformed_directions.colwise().norm() / jacobian.determinant();
251
252 transformed_directions.colwise().normalize();
253
254 if (modulate) {
255 Eigen::MatrixXd temp = aPSF_weights_to_FOD_transform (max_n_SHvox, transformed_directions);
256 for (ssize_t i = 0; i < n_dirs; ++i)
257 temp.col(i) = temp.col(i) * modulation_factors(0,i);
258
260 // TODO: make this work?
261 // transform.noalias() = (aPSF_weights_to_FOD_transform (max_n_SHvox, transformed_directions).colwise() * modulation_factors.transpose()) * map_FOD_to_aPSF_transform[max_n_SHvox];
262 } else {
264 }
265
266 // reorient voxels that contain an FOD
267 for (auto const & sn : start_nvols)
268 if (fod[sn[0]] > 0.0)
269 fod.segment(sn[0],sn[1]) = transform.block(0,0,sn[1],sn[1]) * fod.segment(sn[0],sn[1]);
270
271 image.index(3) = 0; // TODO do we need this?
272 image.row(3) = fod;
273 }
274
275 protected:
276 const ssize_t max_n_SH, n_dirs;
278 const Eigen::MatrixXd& directions;
279 const bool modulate;
281 Eigen::VectorXd fod;
282 std::map<ssize_t, Eigen::MatrixXd> map_FOD_to_aPSF_transform;
283 ssize_t max_n_SHvox;
284 Eigen::MatrixXd transform;
285 Eigen::MatrixXd modulation_factors;
286 };
287
288
289 template <class FODImageType>
291
292 public:
293 NonLinearKernel (const ssize_t n_SH, Image<default_type>& warp, const Eigen::MatrixXd& directions, const bool modulate) :
294 n_SH (n_SH),
299 fod (n_SH) {}
300
301
302 void operator() (FODImageType& image) {
303 image.index(3) = 0;
304 if (image.value() > 0) { // only reorient voxels that contain a FOD
305 for (size_t dim = 0; dim < 3; ++dim)
306 jacobian_adapter.index(dim) = image.index(dim);
307 Eigen::MatrixXd jacobian = jacobian_adapter.value().inverse().template cast<default_type>();
308 Eigen::MatrixXd transformed_directions = jacobian * directions;
309
310 if (modulate) {
311 Eigen::MatrixXd modulation_factors = transformed_directions.colwise().norm() / jacobian.determinant();
312 transformed_directions.colwise().normalize();
313
314 Eigen::MatrixXd temp = aPSF_weights_to_FOD_transform (n_SH, transformed_directions);
315 for (ssize_t i = 0; i < temp.cols(); ++i)
316 temp.col(i) = temp.col(i) * modulation_factors(0,i);
317
318 transform.noalias() = temp * FOD_to_aPSF_transform;
319 } else {
320 transformed_directions.colwise().normalize();
321 transform.noalias() = aPSF_weights_to_FOD_transform (n_SH, transformed_directions) * FOD_to_aPSF_transform;
322 }
323 fod = image.row(3);
324 fod = transform * fod;
325 image.row(3) = fod;
326 }
327 }
328 protected:
329 const ssize_t n_SH;
331 const Eigen::MatrixXd& directions;
332 const bool modulate;
333 const Eigen::MatrixXd FOD_to_aPSF_transform;
334 Eigen::MatrixXd transform;
335 Eigen::VectorXd fod;
336 };
337
338
339 template <class FODImageType>
340 void reorient_warp (const std::string progress_message,
341 FODImageType& fod_image,
343 const Eigen::MatrixXd& directions,
344 const bool modulate = false,
346 {
347 assert (directions.cols() > directions.rows());
348 check_dimensions (fod_image, warp, 0, 3);
349 vector<vector<ssize_t>> start_nvols;
350 size_t max_n_SH (0);
351 if (multi_contrast_settings.size())
352 start_nvols = multiContrastSetting2start_nvols (multi_contrast_settings, max_n_SH);
353 if (start_nvols.size()) {
354 DEBUG ("reorienting warp using MultiContrast NonLinearKernel");
355 ThreadedLoop (progress_message, fod_image, 0, 3)
356 .run (NonLinearKernelMultiContrast<FODImageType>(fod_image.size(3), (ssize_t) max_n_SH, warp, directions, start_nvols, modulate), fod_image);
357 } else {
358 DEBUG ("reorienting warp using NonLinearKernel");
359 ThreadedLoop (progress_message, fod_image, 0, 3)
360 .run (NonLinearKernel<FODImageType>(fod_image.size(3), warp, directions, modulate), fod_image);
361 }
362 }
363
364 template <class FODImageType>
365 void reorient_warp (FODImageType& fod_image,
367 const Eigen::MatrixXd& directions,
368 const bool modulate = false,
370 {
371 assert (directions.cols() > directions.rows());
372 check_dimensions (fod_image, warp, 0, 3);
373 vector<vector<ssize_t>> start_nvols;
374 size_t max_n_SH (0);
375 if (multi_contrast_settings.size())
376 start_nvols = multiContrastSetting2start_nvols (multi_contrast_settings, max_n_SH);
377
378 if (start_nvols.size()) {
379 DEBUG ("reorienting warp using MultiContrast NonLinearKernel");
380 ThreadedLoop (fod_image, 0, 3)
381 .run (NonLinearKernelMultiContrast<FODImageType>(fod_image.size(3), (ssize_t) max_n_SH, warp, directions, start_nvols, modulate), fod_image);
382 } else {
383 DEBUG ("reorienting warp using NonLinearKernel");
384 ThreadedLoop (fod_image, 0, 3)
385 .run (NonLinearKernel<FODImageType>(fod_image.size(3), warp, directions, modulate), fod_image);
386 }
387 }
388
389
390 }
391 }
392}
393
394#endif
a class to hold the coefficients for an apodised point-spread function.
Definition: SH.h:653
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor > transform
Definition: reorient.h:144
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor > transform
Definition: reorient.h:104
Adapter::Jacobian< Image< default_type > > jacobian_adapter
Definition: reorient.h:330
const Eigen::MatrixXd FOD_to_aPSF_transform
Definition: reorient.h:333
std::map< ssize_t, Eigen::MatrixXd > map_FOD_to_aPSF_transform
Definition: reorient.h:282
Adapter::Jacobian< Image< default_type > > jacobian_adapter
Definition: reorient.h:277
const vector< vector< ssize_t > > start_nvols
Definition: reorient.h:280
#define DEBUG(msg)
Definition: exception.h:75
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
Eigen::Matrix< typename MatrixType::Scalar, Eigen::Dynamic, Eigen::Dynamic > init_transform_cart(const MatrixType &dirs, const int lmax)
form the SH->amplitudes matrix
Definition: SH.h:111
MatrixType1 & sconv_mat(MatrixType1 &sh, const VectorType2 &RH)
perform spherical convolution, in place
Definition: SH.h:367
void warp(ImageTypeSource &source, ImageTypeDestination &destination, WarpType &warp, const typename ImageTypeDestination::value_type value_when_out_of_bounds=Interpolator< ImageTypeSource >::default_out_of_bounds_value(), const vector< uint32_t > oversample=Adapter::AutoOverSample, const bool jacobian_modulate=false)
convenience function to warp one image onto another
Definition: warp.h:65
void reorient_warp(const std::string progress_message, FODImageType &fod_image, Image< default_type > &warp, const Eigen::MatrixXd &directions, const bool modulate=false, vector< MultiContrastSetting > multi_contrast_settings=vector< MultiContrastSetting >())
Definition: reorient.h:340
void reorient(FODImageType &input_fod_image, FODImageType &output_fod_image, const transform_type &transform, const Eigen::MatrixXd &directions, bool modulate=false, vector< MultiContrastSetting > multi_contrast_settings=vector< MultiContrastSetting >())
Definition: reorient.h:153
FORCE_INLINE vector< vector< ssize_t > > multiContrastSetting2start_nvols(const vector< MultiContrastSetting > &mcsettings, size_t &max_n_SH)
Definition: reorient.h:43
FORCE_INLINE Eigen::MatrixXd aPSF_weights_to_FOD_transform(const int num_SH, const Eigen::MatrixXd &directions)
Definition: reorient.h:34
Definition: base.h:24
Eigen::Transform< default_type, 3, Eigen::AffineCompact > transform_type
the type for the affine transform of an image:
Definition: types.h:234
ThreadedLoopRunOuter< decltype(Loop(vector< size_t >()))> ThreadedLoop(const HeaderType &source, const vector< size_t > &outer_axes, const vector< size_t > &inner_axes)
Multi-threaded loop object.
#define FORCE_INLINE
Definition: types.h:156