17#ifndef __dwi_tractography_algorithms_iFOD2_h__
18#define __dwi_tractography_algorithms_iFOD2_h__
36 namespace Tractography
56 sin_max_angle_ho (
NaN),
58 mean_truncations (0.0),
59 max_max_truncation (0.0),
66 throw Exception (
"Algorithm iFOD2 expects as input a spherical harmonic (SH) image");
70 throw Exception (
"4th-order Runge-Kutta integration not valid for iFOD2 algorithm");
73 sin_max_angle_ho = std::sin (max_angle_ho);
76 properties[
"method"] =
"iFOD2";
77 properties.set (lmax,
"lmax");
78 properties.set (num_samples,
"samples_per_step");
79 properties.set (max_trials,
"max_trials");
80 fod_power = 1.0/num_samples;
81 properties.set (fod_power,
"fod_power");
82 bool precomputed =
true;
83 properties.set (precomputed,
"sh_precomputed");
85 precomputer.init (lmax);
89 INFO (
"iFOD2 generating " +
str(num_samples) +
" vertices per " +
str (step_size) +
" mm step");
93 size_t downsample_factor = num_samples;
94 properties.set (downsample_factor,
"downsample_factor");
95 downsampler.set_ratio (downsample_factor);
102 const float angle_minradius_preds = 2.0 * std::asin (step_size / (2.0 * min_radius)) / float(num_samples);
104 const float max_step_postds = downsample_factor * step_size / float(num_samples);
105 set_num_points (angle_minradius_preds, max_step_postds);
110 mean_samples /= double(num_proc);
111 mean_truncations /= double(num_proc);
112 INFO (
"mean number of samples per step = " +
str (mean_samples));
113 if (mean_truncations) {
114 INFO (
"mean number of steps between rejection sampling truncations = " +
str (1.0/mean_truncations));
115 INFO (
"maximum truncation error = " +
str (max_max_truncation));
117 INFO (
"no rejection sampling truncations occurred");
121 void update_stats (
double mean_samples_per_run,
double mean_truncations_per_run,
double max_truncation)
const
123 mean_samples += mean_samples_per_run;
124 mean_truncations += mean_truncations_per_run;
125 if (max_truncation > max_max_truncation)
126 max_max_truncation = max_truncation;
130 float internal_step_size()
const override {
return step_size / float(num_samples); }
132 size_t lmax, num_samples, max_trials;
133 float sin_max_angle_ho, fod_power;
137 mutable double mean_samples, mean_truncations, max_max_truncation;
138 mutable int num_proc;
149 iFOD2 (
const Shared& shared) :
156 max_truncation (0.0),
157 positions (S.num_samples),
158 calib_positions (S.num_samples),
159 tangents (S.num_samples),
160 calib_tangents (S.num_samples),
161 sample_idx (S.num_samples)
170 calibrate_ratio (that.calibrate_ratio),
174 max_truncation (0.0),
175 calibrate_list (that.calibrate_list),
176 positions (S.num_samples),
177 calib_positions (S.num_samples),
178 tangents (S.num_samples),
179 calib_tangents (S.num_samples),
180 sample_idx (S.num_samples)
189 S.update_stats (calibrate_list.size() +
float(mean_sample_num)/
float(num_sample_runs),
190 float(num_truncations) /
float(num_sample_runs),
199 if (!get_data (source))
202 if (!S.init_dir.allFinite()) {
204 const Eigen::Vector3f init_dir (dir);
206 for (
size_t n = 0; n < S.max_seed_attempts; n++) {
208 half_log_prob0 = FOD (dir);
209 if (std::isfinite (half_log_prob0) && (half_log_prob0 > S.init_threshold))
216 half_log_prob0 = FOD (dir);
217 if (std::isfinite (half_log_prob0) && (half_log_prob0 > S.init_threshold))
225 half_log_prob0_seed = half_log_prob0 = 0.5 * std::log (half_log_prob0);
226 sample_idx = S.num_samples;
235 if (++sample_idx < S.num_samples) {
236 pos = positions[sample_idx];
237 dir = tangents [sample_idx];
241 Eigen::Vector3f next_pos, next_dir;
244 for (
size_t i = 0; i < calibrate_list.size(); ++i) {
246 float val = path_prob (calib_positions, calib_tangents);
247 if (std::isnan (val))
249 else if (val > max_val)
256 max_val *= calibrate_ratio;
260 for (
size_t n = 0; n < S.max_trials; n++) {
261 float val = rand_path_prob ();
264 DEBUG (
"max_val exceeded!!! (val = " +
str(val) +
", max_val = " +
str (max_val) +
")");
266 if (val/max_val > max_truncation)
267 max_truncation = val/max_val;
271 mean_sample_num += n;
272 half_log_prob0 = last_half_log_probN;
284 float get_metric (
const Eigen::Vector3f& position,
const Eigen::Vector3f& direction)
override
286 if (!get_data (source, position))
288 return FOD (direction);
293 void reverse_track()
override
295 half_log_prob0 = half_log_prob0_seed;
296 sample_idx = S.num_samples;
297 MethodBase::reverse_track();
301 void truncate_track (
GeneratedTrack& tck,
const size_t length_to_revert_from,
const size_t revert_step)
override
304 size_t sample_idx_at_full_length = (length_to_revert_from - tck.get_seed_index()) % S.num_samples;
307 if (!sample_idx_at_full_length)
308 sample_idx_at_full_length = S.num_samples;
309 const size_t points_to_remove = sample_idx_at_full_length + ((revert_step - 1) * S.num_samples);
310 if (tck.get_seed_index() + points_to_remove >= tck.size()) {
316 const size_t new_size = length_to_revert_from - points_to_remove;
317 if (tck.size() == 2 || new_size == 1)
318 dir = (tck[1] - tck[0]).normalized();
319 else if (new_size != tck.size())
320 dir = (tck[new_size] - tck[new_size - 2]).normalized();
321 tck.resize (new_size);
326 half_log_prob0 = 0.5 * std::log (FOD (dir));
329 sample_idx = S.num_samples;
333 act().sgm_depth = (act().sgm_depth > points_to_remove) ? act().sgm_depth - points_to_remove : 0;
341 float calibrate_ratio, half_log_prob0, last_half_log_probN, half_log_prob0_seed;
342 size_t mean_sample_num, num_sample_runs, num_truncations;
343 float max_truncation;
356 FORCE_INLINE float FOD (
const Eigen::Vector3f& direction)
const
358 return (
S.precomputer ?
359 S.precomputer.value (
values, direction) :
364 FORCE_INLINE float FOD (
const Eigen::Vector3f& position,
const Eigen::Vector3f& direction)
366 if (!get_data (source, position))
368 return FOD (direction);
377 return path_prob (positions, tangents);
387 if (!act().fetch_tissue_data (positions[
S.num_samples - 1]))
389 if (act().tissues().get_csf() >= 0.5)
393 float log_prob = half_log_prob0;
394 for (
size_t i = 0; i <
S.num_samples; ++i) {
396 float fod_amp = FOD (positions[i], tangents[i]);
397 if (std::isnan (fod_amp))
399 if (fod_amp <
S.threshold)
401 fod_amp = std::log (fod_amp);
402 if (i <
S.num_samples-1) {
405 last_half_log_probN = 0.5*fod_amp;
406 log_prob += last_half_log_probN;
410 return std::exp (
S.fod_power * log_prob);
417 float cos_theta = end_dir.dot (dir);
418 cos_theta = std::min (cos_theta,
float(1.0));
419 float theta = std::acos (cos_theta);
423 Eigen::Vector3f curv = end_dir - cos_theta * dir;
425 float R =
S.step_size / theta;
427 for (
size_t i = 0; i <
S.num_samples-1; ++i) {
428 float a = (theta * (i+1)) /
S.num_samples;
429 float cos_a = std::cos (a);
430 float sin_a = std::sin (a);
431 positions[i] = pos +
R * (sin_a * dir + (float(1.0) - cos_a) * curv);
432 tangents[i] = cos_a * dir + sin_a * curv;
434 positions[
S.num_samples-1] = pos +
R * (std::sin (theta) * dir + (float(1.0)-cos_theta) * curv);
435 tangents[
S.num_samples-1] = end_dir;
439 for (
size_t i = 0; i <
S.num_samples; ++i) {
440 float f = (i+1) * (
S.step_size /
S.num_samples);
441 positions[i] = pos + f * dir;
458 Calibrate (
iFOD2& method) :
462 positions (P.
S.num_samples),
463 tangents (P.
S.num_samples) {
465 init_log_prob = 0.5 * std::log (
Math::SH::value (P.values, Eigen::Vector3f (0.0, 0.0, 1.0), P.S.lmax));
468 float operator() (
float el)
470 P.pos = { 0.0f, 0.0f, 0.0f };
471 P.get_path (positions, tangents, Eigen::Vector3f (std::sin (
el), 0.0, std::cos(
el)));
473 float log_prob = init_log_prob;
474 for (
size_t i = 0; i < P.S.num_samples; ++i) {
475 float prob =
Math::SH::value (P.values, tangents[i], P.S.lmax) * (1.0 - (positions[i][0] / vox));
478 prob = std::log (prob);
479 if (i < P.S.num_samples-1)
482 log_prob += 0.5*prob;
485 return std::exp (P.S.fod_power * log_prob);
490 Eigen::VectorXf& fod;
493 vector<Eigen::Vector3f> positions, tangents;
496 friend void calibrate<iFOD2> (
iFOD2& method);
friend void calibrate(iFOD2 &method)
FORCE_INLINE Eigen::Vector3f rand_dir(const Eigen::Vector3f &d)
void get_path(vector< Eigen::Vector3f > &positions, vector< Eigen::Vector3f > &tangents, const Eigen::Vector3f &end_dir) const
std::uniform_real_distribution< float > uniform
Eigen::Vector3f rotate_direction(const Eigen::Vector3f &reference, const Eigen::Vector3f &direction)
Eigen::Vector3f random_direction()
Precomputed Associated Legrendre Polynomials - used to speed up SH calculation.
void check(const ImageType &H)
convenience function to check if an input image can contain SH coefficients
size_t LforN(int N)
returns the largest lmax given N parameters
VectorType1 & delta(VectorType1 &delta_vec, const VectorType2 &unit_dir, int lmax)
VectorType::Scalar value(const VectorType &coefs, typename VectorType::Scalar cos_elevation, typename VectorType::Scalar cos_azimuth, typename VectorType::Scalar sin_azimuth, int lmax)
void load_iFOD2_options(Tractography::Properties &)
const App::OptionGroup iFOD2Options
constexpr float angle_ifod2
constexpr float stepsize_voxels_ifod2
constexpr size_t ifod2_nsamples
constexpr size_t max_trials_per_step
constexpr float cutoff_fod
constexpr float cutoff_act_multiplier
thread_local Math::RNG rng
thread-local, but globally accessible RNG to vastly simplify multi-threading
std::string str(const T &value, int precision=0)
constexpr default_type NaN