Developer documentation
Version 3.0.3-105-gd3941f44
exec.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_tractography_tracking_exec_h__
18#define __dwi_tractography_tracking_exec_h__
19
20
21#include "thread.h"
22#include "thread_queue.h"
23#include "dwi/directions/set.h"
31
35
37
38
39#define MAX_NUM_SEED_ATTEMPTS 100000
40
41#define TRACKING_BATCH_SIZE 10
42
43
44
45namespace MR
46{
47 namespace DWI
48 {
49 namespace Tractography
50 {
51 namespace Tracking
52 {
53
54
55 // TODO Try having ACT as a template boolean; allow compiler to optimise out branch statements
56
57 template <class Method> class Exec { MEMALIGN(Exec<Method>)
58
59 public:
60
61 static void run (const std::string& diff_path, const std::string& destination, DWI::Tractography::Properties& properties)
62 {
63
64 if (properties.find ("seed_dynamic") == properties.end()) {
65
66 typename Method::Shared shared (diff_path, properties);
67 WriteKernel writer (shared, destination, properties);
68 Exec<Method> tracker (shared);
70
71 } else {
72
73 const std::string& fod_path (properties["seed_dynamic"]);
74 const std::string max_num_tracks = properties["max_num_tracks"];
75 if (max_num_tracks.empty())
76 throw Exception ("Dynamic seeding requires setting the desired number of tracks using the -select option");
77 const size_t num_tracks = to<size_t>(max_num_tracks);
78
79 using SetDixel = Mapping::SetDixel;
80 using TckMapper = Mapping::TrackMapperBase;
82
84 auto fod_data = Image<float>::open (fod_path);
85 Math::SH::check (fod_data);
86 Seeding::Dynamic* seeder = new Seeding::Dynamic (fod_path, fod_data, num_tracks, dirs);
87 properties.seeds.add (seeder); // List is responsible for deleting this from memory
88
89 typename Method::Shared shared (diff_path, properties);
90
91 Writer writer (shared, destination, properties);
92 Exec<Method> tracker (shared);
93
94 TckMapper mapper (fod_data, dirs);
95 mapper.set_upsample_ratio (Mapping::determine_upsample_ratio (fod_data, properties, 0.25));
96 mapper.set_use_precise_mapping (true);
97
99 Thread::multi (tracker),
101 writer,
103 Thread::multi (mapper),
105 *seeder);
106
107 }
108
109 }
110
111
112
113 Exec (const typename Method::Shared& shared) :
114 S (shared),
115 method (shared),
116 track_excluded (false),
117 include_visitation (S.properties.include, S.properties.ordered_include) { }
118
119 Exec (const Exec& that) :
120 S (that.S),
121 method (that.method),
122 track_excluded (false),
123 include_visitation (S.properties.include, S.properties.ordered_include) { }
124
125
126 bool operator() (GeneratedTrack& item) {
127 if (!seed_track (item))
128 return false;
129 if (track_excluded) {
130 item.set_status (GeneratedTrack::status_t::SEED_REJECTED);
131 S.add_rejection (INVALID_SEED);
132 return true;
133 }
134 gen_track (item);
135 if (track_rejected (item)) {
136 item.clear();
137 item.set_status (GeneratedTrack::status_t::TRACK_REJECTED);
138 } else if (S.downsampler.get_ratio() > 1 || (S.is_act() && S.act().crop_at_gmwmi())) {
139 S.downsampler (item);
140 check_downsampled_length (item);
141 } else {
142 item.set_status (GeneratedTrack::status_t::ACCEPTED);
143 }
144 return true;
145 }
146
147
148 private:
149
150 const typename Method::Shared& S;
151 Method method;
152 bool track_excluded;
153 IncludeROIVisitation include_visitation;
154
155
156 term_t iterate ()
157 {
158 const term_t method_term = (S.rk4 ? next_rk4() : method.next());
159
160 if (method_term)
161 return (S.is_act() && method.act().sgm_depth) ? TERM_IN_SGM : method_term;
162
163 if (S.is_act()) {
164 const term_t structural_term = method.act().check_structural (method.pos);
165 if (structural_term)
166 return structural_term;
167 }
168
169 if (S.properties.mask.size() && !S.properties.mask.contains (method.pos))
170 return EXIT_MASK;
171
172 if (S.properties.exclude.contains (method.pos))
173 return ENTER_EXCLUDE;
174
175 // If backtracking is not enabled, add streamline to include regions as it is generated
176 // If it is enabled, this check can only be performed after the streamline is completed
177 if (!(S.is_act() && S.act().backtrack()))
178 include_visitation (method.pos);
179
180 if (S.stop_on_all_include && bool(include_visitation))
182
183 return CONTINUE;
184 }
185
186
187
188 bool seed_track (GeneratedTrack& tck)
189 {
190 tck.clear();
191 track_excluded = false;
192 include_visitation.reset();
193 method.dir = { NaN, NaN, NaN };
194
195 if (S.properties.seeds.is_finite()) {
196
197 if (!S.properties.seeds.get_seed (method.pos, method.dir))
198 return false;
199 if (!method.check_seed() || !method.init()) {
200 track_excluded = true;
201 tck.set_status (GeneratedTrack::status_t::SEED_REJECTED);
202 }
203 return true;
204
205 } else {
206
207 for (size_t num_attempts = 0; num_attempts != MAX_NUM_SEED_ATTEMPTS; ++num_attempts) {
208 if (S.properties.seeds.get_seed (method.pos, method.dir)) {
209 if (!(method.check_seed() && method.init())) {
210 track_excluded = true;
211 tck.set_status (GeneratedTrack::status_t::SEED_REJECTED);
212 }
213 return true;
214 }
215 }
216 FAIL ("Failed to find suitable seed point after " + str (MAX_NUM_SEED_ATTEMPTS) + " attempts - aborting");
217 return false;
218
219 }
220 }
221
222
223
224 bool gen_track (GeneratedTrack& tck)
225 {
226 bool unidirectional = S.unidirectional;
227 if (S.is_act() && !unidirectional)
228 unidirectional = method.act().seed_is_unidirectional (method.pos, method.dir);
229
230 include_visitation (method.pos);
231
232 const Eigen::Vector3f seed_dir (method.dir);
233 tck.push_back (method.pos);
234
235 gen_track_unidir (tck);
236
237 if (!track_excluded && !unidirectional) {
238 tck.reverse();
239 method.pos = tck.back();
240 method.dir = -seed_dir;
241 method.reverse_track ();
242 gen_track_unidir (tck);
243 }
244
245 return true;
246 }
247
248
249
250
251 void gen_track_unidir (GeneratedTrack& tck)
252 {
253 term_t termination = CONTINUE;
254
255 if (S.is_act() && S.act().backtrack()) {
256
257 size_t revert_step = 1;
258 size_t max_size_at_backtrack = tck.size();
259 unsigned int revert_count = 0;
260
261 do {
262 termination = iterate();
263 if (term_add_to_tck[termination])
264 tck.push_back (method.pos);
265 if (termination) {
266 apply_priors (termination);
267 if (track_excluded && termination != ENTER_EXCLUDE) {
268 if (tck.size() > max_size_at_backtrack) {
269 max_size_at_backtrack = tck.size();
270 revert_step = 1;
271 revert_count = 1;
272 } else {
273 if (revert_count++ == ACT_BACKTRACK_ATTEMPTS) {
274 revert_count = 1;
275 ++revert_step;
276 }
277 }
278 method.truncate_track (tck, max_size_at_backtrack, revert_step);
279 if (method.pos.allFinite()) {
280 track_excluded = false;
281 termination = CONTINUE;
282 }
283 }
284 } else if (tck.size() >= S.max_num_points_preds) {
285 termination = LENGTH_EXCEED;
286 }
287 } while (!termination);
288
289 } else {
290
291 do {
292 termination = iterate();
293 if (term_add_to_tck[termination])
294 tck.push_back (method.pos);
295 if (!termination && tck.size() >= S.max_num_points_preds)
296 termination = LENGTH_EXCEED;
297 } while (!termination);
298
299 }
300
301 apply_priors (termination);
302
303 if (termination == EXIT_SGM) {
304 truncate_exit_sgm (tck);
305 method.pos = tck.back();
306 }
307
308 if (track_excluded) {
309 switch (termination) {
310 case CALIBRATOR: case ENTER_CSF: case MODEL: case HIGH_CURVATURE:
311 S.add_rejection (ACT_POOR_TERMINATION);
312 break;
313 case LENGTH_EXCEED:
314 S.add_rejection (TRACK_TOO_LONG);
315 break;
316 case ENTER_EXCLUDE:
317 S.add_rejection (ENTER_EXCLUDE_REGION);
318 break;
319 default:
320 throw Exception ("\nFIXME: Unidirectional track excluded but termination is good!\n");
321 }
322 }
323
324 if (S.is_act() && (termination == ENTER_CGM) && S.act().crop_at_gmwmi())
325 S.act().crop_at_gmwmi (tck);
326
327#ifdef DEBUG_TERMINATIONS
328 S.add_termination (termination, method.pos);
329#else
330 S.add_termination (termination);
331#endif
332
333 }
334
335
336
337 void apply_priors (term_t& termination)
338 {
339
340 if (S.is_act()) {
341
342 switch (termination) {
343
344 case CONTINUE:
345 throw Exception ("\nFIXME: undefined termination of track in apply_priors()\n");
346
347 case ENTER_CGM: case EXIT_IMAGE: case EXIT_MASK: case EXIT_SGM: case TERM_IN_SGM: case TRAVERSE_ALL_INCLUDE:
348 break;
349
350 case ENTER_CSF: case LENGTH_EXCEED: case ENTER_EXCLUDE:
351 track_excluded = true;
352 break;
353
354 case CALIBRATOR: case MODEL: case HIGH_CURVATURE:
355 if (method.act().sgm_depth)
356 termination = TERM_IN_SGM;
357 else if (!method.act().in_pathology())
358 track_excluded = true;
359 break;
360
361 }
362
363 } else {
364
365 switch (termination) {
366
367 case CONTINUE:
368 throw Exception ("\nFIXME: undefined termination of track in apply_priors()\n");
369
370 case ENTER_CGM: case ENTER_CSF: case EXIT_SGM: case TERM_IN_SGM:
371 throw Exception ("\nFIXME: Have received ACT-based termination for non-ACT tracking in apply_priors()\n");
372
374 break;
375
376 case ENTER_EXCLUDE:
377 track_excluded = true;
378 break;
379
380 }
381
382 }
383
384 }
385
386
387
388 bool track_rejected (const GeneratedTrack& tck)
389 {
390
391 if (track_excluded)
392 return true;
393
394 // seedtest algorithm uses min_num_points_preds = 1; should be 2 or more for all other algorithms
395 if (tck.size() == 1 && S.min_num_points_preds > 1) {
396 S.add_rejection (NO_PROPAGATION_FROM_SEED);
397 return true;
398 }
399
400 if (tck.size() < S.min_num_points_preds) {
401 S.add_rejection (TRACK_TOO_SHORT);
402 return true;
403 }
404
405 if (S.is_act()) {
406
407 if (!satisfy_wm_requirement (tck)) {
408 S.add_rejection (ACT_FAILED_WM_REQUIREMENT);
409 return true;
410 }
411
412 if (S.act().backtrack()) {
413 for (const auto& i : tck)
414 include_visitation (i);
415 }
416
417 }
418
419 if (!bool(include_visitation)) {
420 S.add_rejection (MISSED_INCLUDE_REGION);
421 return true;
422 }
423
424 return false;
425
426 }
427
428 bool satisfy_wm_requirement (const vector<Eigen::Vector3f>& tck)
429 {
430 // If using the Seed_test algorithm (indicated by max_num_points == 2), don't want to execute this check
431 if (S.max_num_points_preds == 2)
432 return true;
433 // If the seed was in SGM, need to confirm that one side of the track actually made it to WM
434 if (method.act().seed_in_sgm && !method.act().sgm_seed_to_wm)
435 return false;
436 // Used these in the ACT paper, but wasn't entirely happy with the method; can change these #defines to re-enable
437 // ACT instead now defaults to a 2-voxel minimum length
439 return true;
440 float integral = 0.0, max_value = 0.0;
441 for (const auto& i : tck) {
442 if (method.act().fetch_tissue_data (i)) {
443 const float wm = method.act().tissues().get_wm();
444 max_value = std::max (max_value, wm);
445 if (((integral += (Math::pow2 (wm) * S.internal_step_size())) > ACT_WM_INT_REQ) && (max_value > ACT_WM_ABS_REQ))
446 return true;
447 }
448 }
449 return false;
450 }
451
452
453
454 void truncate_exit_sgm (vector<Eigen::Vector3f>& tck)
455 {
456 const size_t sgm_start = tck.size() - method.act().sgm_depth;
457 assert (sgm_start >= 0 && sgm_start < tck.size());
458 size_t best_termination = tck.size() - 1;
459 float min_value = std::numeric_limits<float>::infinity();
460 for (size_t i = sgm_start; i != tck.size(); ++i) {
461 const Eigen::Vector3f direction = (tck[i] - tck[i-1]).normalized();
462 const float this_value = method.get_metric (tck[i], direction);
463 if (this_value < min_value) {
464 min_value = this_value;
465 best_termination = i;
466 }
467 }
468 tck.erase (tck.begin() + best_termination + 1, tck.end());
469 }
470
471
472
473 void check_downsampled_length (GeneratedTrack& tck)
474 {
475 // Don't quantify the precise streamline length if we don't have to
476 // (i.e. we know for sure that even in the presence of downsampling,
477 // we're not going to break either of these two criteria)
478 if (tck.size() > S.min_num_points_postds && tck.size() < S.max_num_points_postds) {
479 tck.set_status (GeneratedTrack::status_t::ACCEPTED);
480 return;
481 }
482 const float length = Tractography::length (tck);
483 if (length < S.min_dist) {
484 tck.clear();
485 tck.set_status (GeneratedTrack::status_t::TRACK_REJECTED);
486 S.add_rejection (TRACK_TOO_SHORT);
487 } else if (length > S.max_dist) {
488 if (S.is_act()) {
489 tck.clear();
490 tck.set_status (GeneratedTrack::status_t::TRACK_REJECTED);
491 S.add_rejection (TRACK_TOO_LONG);
492 } else {
493 truncate_maxlength (tck);
494 tck.set_status (GeneratedTrack::status_t::ACCEPTED);
495 }
496 } else {
497 tck.set_status (GeneratedTrack::status_t::ACCEPTED);
498 }
499 }
500
501
502
503 void truncate_maxlength (GeneratedTrack& tck)
504 {
505 // Chop the track short so that the length is precisely the maximum length
506 // If the truncation would result in removing the seed point, truncate all
507 // the way back to the seed point, reverse the streamline, and then
508 // truncate off the end (which used to be the start)
509 float length_sum = 0.0f;
510 size_t index;
511 for (index = 1; index != tck.size(); ++index) {
512 const float seg_length = (tck[index] - tck[index-1]).norm();
513 if (length_sum + seg_length > S.max_dist)
514 break;
515 length_sum += seg_length;
516 }
517
518 // If we don't exceed the maximum length, this function should never have been called!
519 assert (index != tck.size());
520 // But nevertheless; if we happen to somehow get here, let's just allow processing to continue
521 if (index == tck.size())
522 return;
523
524 // Would truncation at this vertex (plus including a new small segment at the end) result in
525 // discarding the seed point? If so:
526 // - Truncate so that the seed point is the last point on the streamline
527 // - Reverse the order of the vertices
528 // - Re-run this function recursively in order to truncate from the opposite end of the streamline
529 if (tck.get_seed_index() >= index) {
530 tck.resize (tck.get_seed_index()+1);
531 tck.reverse();
532 truncate_maxlength (tck);
533 return;
534 }
535
536 // We want to determine a new vertex in between "index" and the prior vertex, which
537 // is of the appropriate distance away from "index" in order to make the streamline
538 // precisely the maximum length
539 tck.resize (index+1);
540 tck[index] = tck[index-1] + ((tck[index] - tck[index-1]).normalized() * (S.max_dist - length_sum));
541 }
542
543
544
545 term_t next_rk4()
546 {
547 term_t termination = CONTINUE;
548 const Eigen::Vector3f init_pos (method.pos);
549 const Eigen::Vector3f init_dir (method.dir);
550 if ((termination = method.next()))
551 return termination;
552 const Eigen::Vector3f dir_rk1 (method.dir);
553 method.pos = init_pos + (dir_rk1 * (0.5 * S.step_size));
554 method.dir = init_dir;
555 if ((termination = method.next()))
556 return termination;
557 const Eigen::Vector3f dir_rk2 (method.dir);
558 method.pos = init_pos + (dir_rk2 * (0.5 * S.step_size));
559 method.dir = init_dir;
560 if ((termination = method.next()))
561 return termination;
562 const Eigen::Vector3f dir_rk3 (method.dir);
563 method.pos = init_pos + (dir_rk3 * S.step_size);
564 method.dir = (dir_rk2 + dir_rk3).normalized();
565 if ((termination = method.next()))
566 return termination;
567 const Eigen::Vector3f dir_rk4 (method.dir);
568 method.dir = (dir_rk1 + (dir_rk2 * 2.0) + (dir_rk3 * 2.0) + dir_rk4).normalized();
569 method.pos = init_pos + (method.dir * S.step_size);
570 const Eigen::Vector3f final_pos (method.pos);
571 const Eigen::Vector3f final_dir (method.dir);
572 if ((termination = method.next()))
573 return termination;
574 if (dir_rk1.dot (method.dir) < S.cos_max_angle_ho)
575 return HIGH_CURVATURE;
576 method.pos = final_pos;
577 method.dir = final_dir;
578 return CONTINUE;
579 }
580
581
582
583 };
584
585
586
587
588
589
590
591
592
593 }
594 }
595 }
596}
597
598#endif
599
600
#define ACT_WM_ABS_REQ
Definition: act.h:26
#define ACT_WM_INT_REQ
Definition: act.h:25
#define ACT_BACKTRACK_ATTEMPTS
Definition: act.h:31
void run()
class to handle writing tracks to file, with RAM buffer
Definition: file.h:353
static Image open(const std::string &image_name, bool read_write_if_existing=false)
Definition: image.h:189
#define FAIL(msg)
Definition: exception.h:72
#define MAX_NUM_SEED_ATTEMPTS
Definition: exec.h:39
#define TRACKING_BATCH_SIZE
Definition: exec.h:41
constexpr T pow2(const T &v)
Definition: math.h:53
void check(const ImageType &H)
convenience function to check if an input image can contain SH coefficients
Definition: SH.h:745
__Multi< typename std::remove_reference< Functor >::type > multi(Functor &&functor, size_t nthreads=threads_to_execute())
used to request multiple threads of the corresponding functor
Definition: thread.h:285
void run_queue(Source &&source, const Item &item, Sink &&sink, size_t capacity=128)
convenience function to set up and run a 2-stage multi-threaded pipeline.
__Batch< Item > batch(const Item &, size_t number=128)
used to request batched processing of items
Definition: thread_queue.h:858
size_t determine_upsample_ratio(const Header &, const float, const float)
const uint8_t term_add_to_tck[13]
Definition: types.h:43
PointType::Scalar length(const vector< PointType > &tck)
Definition: streamline.h:134
Definition: base.h:24
std::string str(const T &value, int precision=0)
Definition: mrtrix.h:247
constexpr default_type NaN
Definition: types.h:230
Eigen::MatrixXd S
Item item
size_t index