highres-cortex 6.0.4
propagate_along_field.tcc
Go to the documentation of this file.
1/*
2Copyright CEA (2014).
3Copyright Université Paris XI (2014).
4
5Contributor: Yann Leprince <yann.leprince@ylep.fr>.
6
7This file is part of highres-cortex, a collection of software designed
8to process high-resolution magnetic resonance images of the cerebral
9cortex.
10
11This software is governed by the CeCILL licence under French law and
12abiding by the rules of distribution of free software. You can use,
13modify and/or redistribute the software under the terms of the CeCILL
14licence as circulated by CEA, CNRS and INRIA at the following URL:
15<http://www.cecill.info/>.
16
17As a counterpart to the access to the source code and rights to copy,
18modify and redistribute granted by the licence, users are provided only
19with a limited warranty and the software's author, the holder of the
20economic rights, and the successive licensors have only limited
21liability.
22
23In this respect, the user's attention is drawn to the risks associated
24with loading, using, modifying and/or developing or reproducing the
25software by the user in light of its specific status of scientific
26software, that may mean that it is complicated to manipulate, and that
27also therefore means that it is reserved for developers and experienced
28professionals having in-depth computer knowledge. Users are therefore
29encouraged to load and test the software's suitability as regards their
30requirements in conditions enabling the security of their systems and/or
31data to be ensured and, more generally, to use and operate it in the
32same conditions as regards security.
33
34The fact that you are presently reading this means that you have had
35knowledge of the CeCILL licence and that you accept its terms.
36*/
37
38#include "propagate_along_field.hh"
39
40#include <cassert>
41#include <cmath>
42#include <iostream>
43#include <stdexcept>
44
45#include <boost/shared_ptr.hpp>
46#include <boost/tuple/tuple.hpp>
47
48namespace
49{
50
51using carto::VolumeRef;
52using std::clog;
53using std::endl;
54using std::flush;
55
56template <typename Tlabel>
57struct SeedValueAscensionResult
58{
59 typedef Tlabel result_type;
60 Tlabel operator()(const Tlabel& label, const Point3df& /*point*/, float /*distance*/) const
61 {
62 return label;
63 };
64 static const Tlabel& failure_result()
65 {
66 static const Tlabel failure_res = 0;
67 return failure_res;
68 };
69};
70template <typename Tlabel>
71struct SeedAndPointAscensionResult
72{
73 typedef std::pair<Tlabel, Point3df> result_type;
74 std::pair<Tlabel, Point3df>
75 operator()(const Tlabel& label, const Point3df& point, float /*distance*/) const
76 {
77 return std::make_pair(label, point);
78 };
79 static const std::pair<Tlabel, Point3df>& failure_result()
80 {
81 static const std::pair<Tlabel, Point3df> failure_res
82 = std::make_pair(0, Point3df(-1, -1, -1));
83 return failure_res;
84 }
85};
86
87template <typename Tlabel>
88struct SeedPointDistAscensionResult
89{
90 typedef boost::tuple<Tlabel, Point3df, float> result_type;
91
92 result_type
93 operator()(const Tlabel& label, const Point3df& point, float distance) const
94 {
95 return boost::make_tuple(label, point, distance);
96 };
97 static const result_type& failure_result()
98 {
99 static const result_type failure_res
100 = boost::make_tuple(0, Point3df(-1, -1, -1), 0.0f);
101 return failure_res;
102 }
103};
104
105
106} // end of anonymous namespace
107
108template <template <typename> class ResultChooserTemplate, typename Tlabel>
109typename ResultChooserTemplate<Tlabel>::result_type
110inline
111yl::PropagateAlongField::
112internal_ascension(const Point3df &start_point,
113 const VolumeRef<Tlabel> &seeds,
114 const Tlabel ignore_label,
115 const ResultChooserTemplate<Tlabel>& result_chooser) const
116{
117 return internal_ascension<ResultChooserTemplate<Tlabel> >
118 (start_point, seeds, ignore_label, result_chooser);
119}
120
121template <class ResultChooser, typename Tlabel>
122typename ResultChooser::result_type
123yl::PropagateAlongField::
124internal_ascension(const Point3df &start_point,
125 const VolumeRef<Tlabel> &seeds,
126 const Tlabel ignore_label,
127 const ResultChooser& result_chooser) const
128{
129 if(debug_output >= 3 && m_verbose >= 3) {
130 clog << " ascension at " << start_point << endl;
131 }
132
133 const std::vector<float> voxel_size = seeds->getVoxelSize();
134 const float voxel_size_x = voxel_size[0];
135 const float voxel_size_y = voxel_size[1];
136 const float voxel_size_z = voxel_size[2];
137 const float invsize_x = 1.0f / voxel_size_x;
138 const float invsize_y = 1.0f / voxel_size_y;
139 const float invsize_z = 1.0f / voxel_size_z;
140 if(!std::isnormal(voxel_size_x) ||
141 !std::isnormal(voxel_size_y) ||
142 !std::isnormal(voxel_size_z)) {
143 throw std::runtime_error("inconsistent voxel_size value");
144 }
145
146 Point3df current_point = start_point;
147
148 const int size_x = seeds.getSizeX();
149 const int size_y = seeds.getSizeY();
150 const int size_z = seeds.getSizeZ();
151
152 unsigned int iter;
153 for(iter = 0; iter < m_max_iter ; ++iter) {
154 const float xp = current_point[0],
155 yp = current_point[1], zp = current_point[2];
156
157 const int ix = lround(xp * invsize_x);
158 if(ix < 0 || ix >= size_x) break;
159 const int iy = lround(yp * invsize_y);
160 if(iy < 0 || iy >= size_y) break;
161 const int iz = lround(zp * invsize_z);
162 if(iz < 0 || iz >= size_z) break;
163
164 const Tlabel seed_value = seeds(ix, iy, iz);
165 if(debug_output >= 4 && m_verbose >= 4) {
166 clog << " iteration " << iter << " at " << current_point
167 << ", seed_value = " << seed_value << endl;
168 }
169 if(seed_value != 0 && seed_value != ignore_label) {
170 return result_chooser(seed_value, current_point, std::abs(m_step * iter));
171 }
172
173 // Move along the field
174 Point3df local_field;
175 float& gx = local_field[0], gy = local_field[1], gz = local_field[2];
176 try {
177 m_vector_field->evaluate(current_point, local_field);
178 } catch(const Field::UndefinedField&) {
179 if(m_verbose >= 2) {
180 clog << " ascension at " << start_point << " aborted after "
181 << iter << " iterations: vector field is undefined at ("
182 << gx << ", " << gy << ", " << gz << ")" << endl;
183 }
184 return ResultChooser::failure_result();
185 }
186
187 // Normalize the field, stop if too small or infinite or NaN.
188 const float gn = std::sqrt(gx*gx + gy*gy + gz*gz);
189 if(!std::isnormal(gn)) break;
190 gx /= gn; gy /= gn; gz /= gn;
191
192 current_point[0] = xp + m_step * gx;
193 current_point[1] = yp + m_step * gy;
194 current_point[2] = zp + m_step * gz;
195 }
196
197 if(m_verbose >= 2) {
198 clog << " ascension at " << start_point << " aborted after "
199 << iter << " iterations" << endl;
200 }
201 return ResultChooser::failure_result();
202}
203
204
205class ScalarFieldSumVisitor
206{
207public:
208 explicit ScalarFieldSumVisitor(const boost::shared_ptr<yl::ScalarField>& field)
209 : m_field(field), m_sum(0.f) {};
210
211 void init(const Point3df& /*start_point*/) {};
212 void visit(const Point3df& point)
213 {
214 m_sum += m_field->evaluate(point);
215 };
216
217 float sum() const
218 { return m_sum; };
219private:
220 boost::shared_ptr<yl::ScalarField> m_field;
221 float m_sum;
222};
223
224template <typename Tlabel>
225float yl::PropagateAlongField::
226integrate_field_along_advection(const Point3df& start_point,
227 const VolumeRef<Tlabel>& seeds,
228 const boost::shared_ptr<yl::ScalarField>& field,
229 const Tlabel ignore_label) const
230{
231 ScalarFieldSumVisitor visitor(field);
232 if(visitor_ascension(start_point, seeds, ignore_label, visitor)) {
233 return m_step * visitor.sum();
234 }
235 throw AbortedAdvection();
236}
237
238template <class TVisitor, typename Tlabel>
239bool
240yl::PropagateAlongField::
241visitor_ascension(const Point3df& start_point,
242 const VolumeRef<Tlabel>& seeds,
243 const Tlabel ignore_label,
244 TVisitor& visitor) const
245{
246 if(debug_output >= 3 && m_verbose >= 3) {
247 clog << " ascension at " << start_point << endl;
248 }
249
250 const std::vector<float> voxel_size = seeds->getVoxelSize();
251 const float voxel_size_x = voxel_size[0];
252 const float voxel_size_y = voxel_size[1];
253 const float voxel_size_z = voxel_size[2];
254 const float invsize_x = 1.0f / voxel_size_x;
255 const float invsize_y = 1.0f / voxel_size_y;
256 const float invsize_z = 1.0f / voxel_size_z;
257 if(!std::isnormal(voxel_size_x) ||
258 !std::isnormal(voxel_size_y) ||
259 !std::isnormal(voxel_size_z)) {
260 throw std::runtime_error("inconsistent voxel_size value");
261 }
262
263 Point3df current_point = start_point;
264 visitor.init(start_point);
265
266 const int size_x = seeds.getSizeX();
267 const int size_y = seeds.getSizeY();
268 const int size_z = seeds.getSizeZ();
269
270 unsigned int iter;
271 for(iter = 0; iter < m_max_iter ; ++iter) {
272 const float xp = current_point[0],
273 yp = current_point[1], zp = current_point[2];
274
275 const int ix = lround(xp * invsize_x);
276 if(ix < 0 || ix >= size_x) break;
277 const int iy = lround(yp * invsize_y);
278 if(iy < 0 || iy >= size_y) break;
279 const int iz = lround(zp * invsize_z);
280 if(iz < 0 || iz >= size_z) break;
281
282 const Tlabel seed_value = seeds(ix, iy, iz);
283 if(debug_output >= 4 && m_verbose >= 4) {
284 clog << " iteration " << iter << " at " << current_point
285 << ", seed_value = " << seed_value << endl;
286 }
287 visitor.visit(current_point);
288 if(seed_value != 0 && seed_value != ignore_label) {
289 return true;
290 }
291
292 // Move along the field
293 Point3df local_field;
294 float& gx = local_field[0], gy = local_field[1], gz = local_field[2];
295 try {
296 m_vector_field->evaluate(current_point, local_field);
297 } catch(const Field::UndefinedField&) {
298 if(m_verbose >= 2) {
299 clog << " ascension at " << start_point << " aborted after "
300 << iter << " iterations: vector field is undefined at ("
301 << gx << ", " << gy << ", " << gz << ")" << endl;
302 }
303 return false;
304 }
305
306 // Normalize the field, stop if too small or infinite or NaN.
307 const float gn = std::sqrt(gx*gx + gy*gy + gz*gz);
308 if(!std::isnormal(gn)) break;
309 gx /= gn; gy /= gn; gz /= gn;
310
311 current_point[0] = xp + m_step * gx;
312 current_point[1] = yp + m_step * gy;
313 current_point[2] = zp + m_step * gz;
314 }
315
316 if(m_verbose >= 2) {
317 clog << " ascension at " << start_point << " aborted after "
318 << iter << " iterations" << endl;
319 }
320 return false;
321}
322
323
324template <typename Tlabel>
325Tlabel
326yl::PropagateAlongField::
327ascend_until_nonzero(const Point3df &start_point,
328 const VolumeRef<Tlabel> &seeds,
329 const Tlabel ignore_label) const
330{
331 return internal_ascension(start_point, seeds, ignore_label,
332 SeedValueAscensionResult<Tlabel>());
333}
334
335
336namespace {
337
338template <typename Tlabel>
339class SeedValueRecorder
340{
341public:
342 explicit SeedValueRecorder(const VolumeRef<Tlabel>& seeds)
343 : m_seed_values(new carto::Volume<Tlabel>(*seeds)) {};
344
345 void record(const int x, const int y, const int z,
346 const std::pair<Tlabel, Point3df>& value)
347 {
348 m_seed_values(x, y, z) = value.first;
349 };
350
351 VolumeRef<Tlabel> result() const {return m_seed_values;};
352
353private:
354 VolumeRef<Tlabel> m_seed_values;
355};
356
357template <typename Tlabel>
358class SeedValueAndPointRecorder
359{
360public:
361 explicit SeedValueAndPointRecorder(const VolumeRef<Tlabel>& seeds)
362 : m_seed_values(new carto::Volume<Tlabel>(*seeds)),
363 m_points(seeds.getSizeX(), seeds.getSizeY(), seeds.getSizeZ(), 3)
364 {
365 m_points->copyHeaderFrom(seeds.header());
366 m_points.fill(-1);
367 assert(m_points.getSizeT() == 3);
368 };
369
370 void record(const int x, const int y, const int z,
371 const std::pair<Tlabel, Point3df>& value)
372 {
373 m_seed_values(x, y, z) = value.first;
374 const Point3df& point = value.second;
375 m_points(x, y, z, 0) = point[0];
376 m_points(x, y, z, 1) = point[1];
377 m_points(x, y, z, 2) = point[2];
378 };
379
380 std::pair<VolumeRef<Tlabel>, VolumeRef<float> >
381 result() const {return std::make_pair(m_seed_values, m_points);};
382
383private:
384 VolumeRef<Tlabel> m_seed_values;
385 VolumeRef<float> m_points;
386};
387
388} // end of anonymous namespace
389
390
391template<typename Tlabel>
392carto::VolumeRef<Tlabel>
393yl::PropagateAlongField::
394propagate_regions(const carto::VolumeRef<Tlabel> &seeds,
395 const Tlabel target_label) const
396{
397 SeedValueRecorder<Tlabel> result_recorder(seeds);
398 internal_propagation(seeds, target_label, result_recorder);
399 return result_recorder.result();
400}
401
402template<typename Tlabel>
403std::pair<carto::VolumeRef<Tlabel>, carto::VolumeRef<float> >
404yl::PropagateAlongField::
405propagate_regions_keeping_dests(const carto::VolumeRef<Tlabel> &seeds,
406 const Tlabel target_label) const
407{
408 SeedValueAndPointRecorder<Tlabel> result_recorder(seeds);
409 internal_propagation(seeds, target_label, result_recorder);
410 return result_recorder.result();
411}
412
413template<class ResultRecorder, typename Tlabel>
414void
415yl::PropagateAlongField::
416internal_propagation(const carto::VolumeRef<Tlabel> &seeds,
417 const Tlabel target_label,
418 ResultRecorder& result_recorder) const
419{
420 SeedAndPointAscensionResult<Tlabel> ascension_result_chooser;
421
422 const int size_x = seeds.getSizeX();
423 const int size_y = seeds.getSizeY();
424 const int size_z = seeds.getSizeZ();
425
426 const std::vector<float> voxel_size = seeds->getVoxelSize();
427 const float voxel_size_x = voxel_size[0];
428 const float voxel_size_y = voxel_size[1];
429 const float voxel_size_z = voxel_size[2];
430
431 if(m_verbose) {
432 std::clog << "yl::PropagateAlongField::propagate_regions:\n"
433 " maximum propagation distance: "
434 << std::abs(m_step * m_max_iter) << " mm." << std::endl;
435 }
436
437 unsigned int n_propagated = 0, n_dead_end = 0, n_lost = 0;
438
439 int slices_done = 0;
440 #pragma omp parallel for schedule(dynamic)
441 for(int z = 0; z < size_z; ++z)
442 {
443 for(int y = 0; y < size_y; ++y)
444 for(int x = 0; x < size_x; ++x)
445 {
446 if(seeds(x, y, z) == target_label) {
447 const Point3df point(x * voxel_size_x,
448 y * voxel_size_y,
449 z * voxel_size_z);
450
451 std::pair<Tlabel, Point3df> result
452 = internal_ascension(point, seeds, target_label,
453 ascension_result_chooser);
454 const Tlabel& result_label = result.first;
455
456 if(result_label > 0) {
457 result_recorder.record(x, y, z, result);
458 #pragma omp atomic
459 ++n_propagated;
460 } else if(result_label < 0) {
461 #pragma omp atomic
462 ++n_dead_end;
463 } else { // result_label == 0
464 #pragma omp atomic
465 ++n_lost;
466 }
467 }
468 }
469
470 #pragma omp atomic
471 ++slices_done;
472
473 if(m_verbose) {
474 #pragma omp critical(print_stderr)
475 std::clog << "\r " << slices_done << " / "
476 << size_z << " slices processed. "
477 << n_propagated << " voxels propagated, "
478 << n_dead_end << " dead-end, "
479 << n_lost << " lost..." << std::flush;
480 }
481 }
482
483 if(m_verbose) {
484 std::clog << "\nyl::PropagateAlongField::propagate_regions: "
485 << n_propagated << " propagated, "
486 << n_dead_end << " dead-end, "
487 << n_lost << " lost." << std::endl;
488 }
489}