Developer documentation
Version 3.0.3-105-gd3941f44
fmls.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_fmls_h__
18#define __dwi_fmls_h__
19
20#include <map> // Used for sorting FOD samples
21
22#include "memory.h"
23#include "math/SH.h"
24#include "dwi/directions/set.h"
25#include "dwi/directions/mask.h"
26#include "image.h"
27#include "algo/loop.h"
28
29
30
31#define FMLS_INTEGRAL_THRESHOLD_DEFAULT 0.0 // By default, don't threshold by integral (tough to get a good number)
32#define FMLS_PEAK_VALUE_THRESHOLD_DEFAULT 0.1
33#define FMLS_MERGE_RATIO_BRIDGE_TO_PEAK_DEFAULT 1.0 // By default, never perform merging of lobes generated from discrete peaks such that a single lobe contains multiple peaks
34
35
36// By default, the mean direction of each FOD lobe is calculated by taking a weighted average of the
37// Euclidean unit vectors (weights are FOD amplitudes). This is not strictly mathematically correct, and
38// a method is provided for optimising the mean direction estimate based on minimising the weighted sum of squared
39// geodesic distances on the sphere. However the coarse estimate is in fact accurate enough for our applications.
40// Uncomment this line to activate the optimal mean direction calculation (about a 20% performance penalty).
41//#define FMLS_OPTIMISE_MEAN_DIR
42
43
44
45namespace MR
46{
47 namespace DWI
48 {
49 namespace FMLS
50 {
51
52
55
56
57 class Segmenter;
58
59 // These are for configuring the FMLS segmenter at the command line, particularly for fod_metric command
62
63
64 class FOD_lobe { MEMALIGN(FOD_lobe)
65
66 public:
67 FOD_lobe (const DWI::Directions::Set& dirs, const index_type seed, const default_type value, const default_type weight) :
68 mask (dirs),
69 values (Eigen::Array<default_type, Eigen::Dynamic, 1>::Zero (dirs.size())),
70 max_peak_value (abs (value)),
71 peak_dirs (1, dirs.get_dir (seed)),
72 mean_dir (peak_dirs.front() * abs(value) * weight),
73 integral (abs (value * weight)),
74 neg (value <= 0.0)
75 {
76 mask[seed] = true;
77 values[seed] = value;
78 }
79
80 // This is used for creating a `null lobe' i.e. an FOD lobe with zero size, containing all directions not
81 // assigned to any other lobe in the voxel
83 mask (i),
84 values (Eigen::Array<default_type, Eigen::Dynamic, 1>::Zero (i.size())),
85 max_peak_value (0.0),
86 integral (0.0),
87 neg (false) { }
88
89
90 void add (const index_type bin, const default_type value, const default_type weight)
91 {
92 assert ((value <= 0.0 && neg) || (value >= 0.0 && !neg));
93 mask[bin] = true;
94 values[bin] = value;
95 const Eigen::Vector3d& dir = mask.get_dirs()[bin];
96 const default_type multiplier = (mean_dir.dot (dir)) > 0.0 ? 1.0 : -1.0;
97 mean_dir += dir * multiplier * abs(value) * weight;
98 integral += abs (value * weight);
99 }
100
101 void revise_peak (const size_t index, const Eigen::Vector3d& revised_peak_dir, const default_type revised_peak_value)
102 {
103 assert (!neg);
104 assert (index < num_peaks());
105 peak_dirs[index] = revised_peak_dir;
106 if (!index)
107 max_peak_value = revised_peak_value;
108 }
109
110#ifdef FMLS_OPTIMISE_MEAN_DIR
111 void revise_mean_dir (const Eigen::Vector3f& real_mean)
112 {
113 assert (!neg);
114 mean_dir = real_mean;
115 }
116#endif
117
118 void finalise()
119 {
120 // This is calculated as the lobe is built; just needs to be set to unit length
121 mean_dir.normalize();
122 }
123
124 void merge (const FOD_lobe& that)
125 {
126 assert (neg == that.neg);
127 mask |= that.mask;
128 for (size_t i = 0; i != mask.size(); ++i)
129 values[i] += that.values[i];
130 if (that.max_peak_value > max_peak_value) {
131 max_peak_value = that.max_peak_value;
132 peak_dirs.insert (peak_dirs.begin(), that.peak_dirs.begin(), that.peak_dirs.end());
133 } else {
134 peak_dirs.insert (peak_dirs.end(), that.peak_dirs.begin(), that.peak_dirs.end());
135 }
136 const default_type multiplier = (mean_dir.dot (that.mean_dir)) > 0.0 ? 1.0 : -1.0;
137 mean_dir += that.mean_dir * that.integral * multiplier;
138 integral += that.integral;
139 }
140
141 const DWI::Directions::Mask& get_mask() const { return mask; }
142 const Eigen::Array<default_type, Eigen::Dynamic, 1>& get_values() const { return values; }
143 default_type get_max_peak_value() const { return max_peak_value; }
144 size_t num_peaks() const { return peak_dirs.size(); }
145 const Eigen::Vector3d& get_peak_dir (const size_t i) const { assert (i < num_peaks()); return peak_dirs[i]; }
146 const Eigen::Vector3d& get_mean_dir() const { return mean_dir; }
147 default_type get_integral() const { return integral; }
148 bool is_negative() const { return neg; }
149
150
151 private:
153 Eigen::Array<default_type, Eigen::Dynamic, 1> values;
154 default_type max_peak_value;
155 vector<Eigen::Vector3d> peak_dirs;
156 Eigen::Vector3d mean_dir;
157 default_type integral;
158 bool neg;
159
160 };
161
162
163
164 class FOD_lobes : public vector<FOD_lobe> { MEMALIGN(FOD_lobes)
165 public:
166 Eigen::Array3i vox;
167 vector<uint8_t> lut;
168 };
169
170
171 class SH_coefs : public Eigen::Matrix<default_type, Eigen::Dynamic, 1> { MEMALIGN(SH_coefs)
172 public:
173 SH_coefs() :
174 vox (-1, -1, -1) { }
175 SH_coefs (const Eigen::Matrix<default_type, Eigen::Dynamic, 1>& that) :
176 Eigen::Matrix<default_type, Eigen::Dynamic, 1> (that),
177 vox (-1, -1, -1) { }
178 Eigen::Array3i vox;
179 };
180
182 { MEMALIGN (FODQueueWriter)
183
184 using FODImageType = Image<float>;
186
187 public:
188 FODQueueWriter (const FODImageType& fod_image, const MaskImageType& mask_image = MaskImageType()) :
189 fod (fod_image),
190 mask (mask_image),
191 loop (Loop("segmenting FODs", 0, 3) (fod)) { }
192
194 {
195 if (!loop)
196 return false;
197 if (mask.valid()) {
198 do {
199 assign_pos_of (fod, 0, 3).to (mask);
200 if (!mask.value())
201 ++loop;
202 } while (loop && !mask.value());
203 }
204 assign_pos_of (fod).to (out.vox);
205 out.resize (fod.size (3));
206 for (auto l = Loop (3) (fod); l; ++l)
207 out[fod.index(3)] = fod.value();
208 ++loop;
209 return true;
210 }
211
212 private:
213 FODImageType fod;
214 MaskImageType mask;
215 decltype(Loop("text", 0, 3) (fod)) loop;
216
217 };
218
219
220 // Store a vector of weights to be applied when computing integrals, to account for non-uniformities in direction distribution
221 // These weights are applied to the amplitude along each direction as the integral for each lobe is summed,
222 // in order to take into account the relative spacing between adjacent directions
225 public:
227 default_type operator[] (const size_t i) { assert (i < size_t(data.size())); return data[i]; }
228 private:
229 Eigen::Array<default_type, Eigen::Dynamic, 1> data;
230 };
231
232
233
234
236
237 public:
238 Segmenter (const DWI::Directions::FastLookupSet&, const size_t);
239
240 bool operator() (const SH_coefs&, FOD_lobes&) const;
241
242
243 default_type get_integral_threshold () const { return integral_threshold; }
244 void set_integral_threshold (const default_type i) { integral_threshold = i; }
245 default_type get_peak_value_threshold () const { return peak_value_threshold; }
246 void set_peak_value_threshold (const default_type i) { peak_value_threshold = i; }
247 default_type get_lobe_merge_ratio () const { return lobe_merge_ratio; }
248 void set_lobe_merge_ratio (const default_type i) { lobe_merge_ratio = i; }
249 bool get_create_null_lobe () const { return create_null_lobe; }
250 void set_create_null_lobe (const bool i) { create_null_lobe = i; verify_settings(); }
251 bool get_create_lookup_table () const { return create_lookup_table; }
252 void set_create_lookup_table (const bool i) { create_lookup_table = i; verify_settings(); }
253 bool get_dilate_lookup_table () const { return dilate_lookup_table; }
254 void set_dilate_lookup_table (const bool i) { dilate_lookup_table = i; verify_settings(); }
255
256
257 private:
258
259 // FastLookupSet is required for ensuring that when a peak direction is
260 // revised using Newton optimisation, that direction is still reflective
261 // of the original peak; i.e. it hasn't 'leaped' across to a different peak
263
264 const size_t lmax;
265
266 std::shared_ptr<Math::SH::Transform <default_type>> transform;
267 std::shared_ptr<Math::SH::PrecomputedAL<default_type>> precomputer;
268 std::shared_ptr<IntegrationWeights> weights;
269
270 default_type integral_threshold; // Integral of positive lobe must be at least this value
271 default_type peak_value_threshold; // Absolute threshold for the peak amplitude of the lobe
272 default_type lobe_merge_ratio; // Determines whether two lobes get agglomerated into one, depending on the FOD amplitude at the current point and how it compares to the maximal amplitudes of the lobes to which it could be assigned
273 bool create_null_lobe; // If this is set, an additional lobe will be created after segmentation with zero size, containing all directions not assigned to any other lobe
274 bool create_lookup_table; // If this is set, an additional lobe will be created after segmentation with zero size, containing all directions not assigned to any other lobe
275 bool dilate_lookup_table; // If this is set, the lookup table created for each voxel will be dilated so that all directions correspond to the nearest positive non-zero FOD lobe
276
277
278 void verify_settings() const
279 {
280 if (create_null_lobe && dilate_lookup_table)
281 throw Exception ("For FOD segmentation, options 'create_null_lobe' and 'dilate_lookup_table' are mutually exclusive");
282 if (!create_lookup_table && dilate_lookup_table)
283 throw Exception ("For FOD segmentation, 'create_lookup_table' must be set in order for lookup tables to be dilated ('dilate_lookup_table')");
284 }
285
286#ifdef FMLS_OPTIMISE_MEAN_DIR
287 void optimise_mean_dir (FOD_lobe&) const;
288#endif
289
290 };
291
292
293
294
295 }
296 }
297}
298
299
300#endif
a class to hold a named list of Option's
size_t size() const
the number of boolean elements in the set
Definition: bitset.h:120
const Set & get_dirs() const
Definition: mask.h:46
FODQueueWriter(const FODImageType &fod_image, const MaskImageType &mask_image=MaskImageType())
Definition: fmls.h:188
bool operator()(SH_coefs &out)
Definition: fmls.h:193
bool valid() const
Definition: image.h:56
Matrix(const MR::Helper::ConstRow< ImageType > &row)
Definition: matrix.h:17
FORCE_INLINE LoopAlongAxes Loop()
Definition: loop.h:419
VectorType::Scalar value(const VectorType &coefs, typename VectorType::Scalar cos_elevation, typename VectorType::Scalar cos_azimuth, typename VectorType::Scalar sin_azimuth, int lmax)
Definition: SH.h:233
unsigned int index_type
Definition: set.h:34
void load_fmls_thresholds(Segmenter &)
const App::OptionGroup FMLSSegmentOption
uint32_t index_type
Definition: types.h:25
Definition: base.h:24
double default_type
the default type used throughout MRtrix
Definition: types.h:228
constexpr std::enable_if< std::is_arithmetic< X >::value &&std::is_unsigned< X >::value, X >::type abs(X x)
Definition: types.h:297
size_t index
#define MEMALIGN(...)
Definition: types.h:185