3Copyright Université Paris XI (2014).
5Contributor: Yann Leprince <yann.leprince@ylep.fr>.
7This file is part of highres-cortex, a collection of software designed
8to process high-resolution magnetic resonance images of the cerebral
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/>.
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
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.
34The fact that you are presently reading this means that you have had
35knowledge of the CeCILL licence and that you accept its terms.
38#include "propagate_along_field.hh"
45#include <boost/shared_ptr.hpp>
46#include <boost/tuple/tuple.hpp>
51using carto::VolumeRef;
56template <typename Tlabel>
57struct SeedValueAscensionResult
59 typedef Tlabel result_type;
60 Tlabel operator()(const Tlabel& label, const Point3df& /*point*/, float /*distance*/) const
64 static const Tlabel& failure_result()
66 static const Tlabel failure_res = 0;
70template <typename Tlabel>
71struct SeedAndPointAscensionResult
73 typedef std::pair<Tlabel, Point3df> result_type;
74 std::pair<Tlabel, Point3df>
75 operator()(const Tlabel& label, const Point3df& point, float /*distance*/) const
77 return std::make_pair(label, point);
79 static const std::pair<Tlabel, Point3df>& failure_result()
81 static const std::pair<Tlabel, Point3df> failure_res
82 = std::make_pair(0, Point3df(-1, -1, -1));
87template <typename Tlabel>
88struct SeedPointDistAscensionResult
90 typedef boost::tuple<Tlabel, Point3df, float> result_type;
93 operator()(const Tlabel& label, const Point3df& point, float distance) const
95 return boost::make_tuple(label, point, distance);
97 static const result_type& failure_result()
99 static const result_type failure_res
100 = boost::make_tuple(0, Point3df(-1, -1, -1), 0.0f);
106} // end of anonymous namespace
108template <template <typename> class ResultChooserTemplate, typename Tlabel>
109typename ResultChooserTemplate<Tlabel>::result_type
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
117 return internal_ascension<ResultChooserTemplate<Tlabel> >
118 (start_point, seeds, ignore_label, result_chooser);
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
129 if(debug_output >= 3 && m_verbose >= 3) {
130 clog << " ascension at " << start_point << endl;
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");
146 Point3df current_point = start_point;
148 const int size_x = seeds.getSizeX();
149 const int size_y = seeds.getSizeY();
150 const int size_z = seeds.getSizeZ();
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];
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;
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;
169 if(seed_value != 0 && seed_value != ignore_label) {
170 return result_chooser(seed_value, current_point, std::abs(m_step * iter));
173 // Move along the field
174 Point3df local_field;
175 float& gx = local_field[0], gy = local_field[1], gz = local_field[2];
177 m_vector_field->evaluate(current_point, local_field);
178 } catch(const Field::UndefinedField&) {
180 clog << " ascension at " << start_point << " aborted after "
181 << iter << " iterations: vector field is undefined at ("
182 << gx << ", " << gy << ", " << gz << ")" << endl;
184 return ResultChooser::failure_result();
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;
192 current_point[0] = xp + m_step * gx;
193 current_point[1] = yp + m_step * gy;
194 current_point[2] = zp + m_step * gz;
198 clog << " ascension at " << start_point << " aborted after "
199 << iter << " iterations" << endl;
201 return ResultChooser::failure_result();
205class ScalarFieldSumVisitor
208 explicit ScalarFieldSumVisitor(const boost::shared_ptr<yl::ScalarField>& field)
209 : m_field(field), m_sum(0.f) {};
211 void init(const Point3df& /*start_point*/) {};
212 void visit(const Point3df& point)
214 m_sum += m_field->evaluate(point);
220 boost::shared_ptr<yl::ScalarField> m_field;
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
231 ScalarFieldSumVisitor visitor(field);
232 if(visitor_ascension(start_point, seeds, ignore_label, visitor)) {
233 return m_step * visitor.sum();
235 throw AbortedAdvection();
238template <class TVisitor, typename Tlabel>
240yl::PropagateAlongField::
241visitor_ascension(const Point3df& start_point,
242 const VolumeRef<Tlabel>& seeds,
243 const Tlabel ignore_label,
244 TVisitor& visitor) const
246 if(debug_output >= 3 && m_verbose >= 3) {
247 clog << " ascension at " << start_point << endl;
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");
263 Point3df current_point = start_point;
264 visitor.init(start_point);
266 const int size_x = seeds.getSizeX();
267 const int size_y = seeds.getSizeY();
268 const int size_z = seeds.getSizeZ();
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];
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;
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;
287 visitor.visit(current_point);
288 if(seed_value != 0 && seed_value != ignore_label) {
292 // Move along the field
293 Point3df local_field;
294 float& gx = local_field[0], gy = local_field[1], gz = local_field[2];
296 m_vector_field->evaluate(current_point, local_field);
297 } catch(const Field::UndefinedField&) {
299 clog << " ascension at " << start_point << " aborted after "
300 << iter << " iterations: vector field is undefined at ("
301 << gx << ", " << gy << ", " << gz << ")" << endl;
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;
311 current_point[0] = xp + m_step * gx;
312 current_point[1] = yp + m_step * gy;
313 current_point[2] = zp + m_step * gz;
317 clog << " ascension at " << start_point << " aborted after "
318 << iter << " iterations" << endl;
324template <typename Tlabel>
326yl::PropagateAlongField::
327ascend_until_nonzero(const Point3df &start_point,
328 const VolumeRef<Tlabel> &seeds,
329 const Tlabel ignore_label) const
331 return internal_ascension(start_point, seeds, ignore_label,
332 SeedValueAscensionResult<Tlabel>());
338template <typename Tlabel>
339class SeedValueRecorder
342 explicit SeedValueRecorder(const VolumeRef<Tlabel>& seeds)
343 : m_seed_values(new carto::Volume<Tlabel>(*seeds)) {};
345 void record(const int x, const int y, const int z,
346 const std::pair<Tlabel, Point3df>& value)
348 m_seed_values(x, y, z) = value.first;
351 VolumeRef<Tlabel> result() const {return m_seed_values;};
354 VolumeRef<Tlabel> m_seed_values;
357template <typename Tlabel>
358class SeedValueAndPointRecorder
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)
365 m_points->copyHeaderFrom(seeds.header());
367 assert(m_points.getSizeT() == 3);
370 void record(const int x, const int y, const int z,
371 const std::pair<Tlabel, Point3df>& value)
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];
380 std::pair<VolumeRef<Tlabel>, VolumeRef<float> >
381 result() const {return std::make_pair(m_seed_values, m_points);};
384 VolumeRef<Tlabel> m_seed_values;
385 VolumeRef<float> m_points;
388} // end of anonymous namespace
391template<typename Tlabel>
392carto::VolumeRef<Tlabel>
393yl::PropagateAlongField::
394propagate_regions(const carto::VolumeRef<Tlabel> &seeds,
395 const Tlabel target_label) const
397 SeedValueRecorder<Tlabel> result_recorder(seeds);
398 internal_propagation(seeds, target_label, result_recorder);
399 return result_recorder.result();
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
408 SeedValueAndPointRecorder<Tlabel> result_recorder(seeds);
409 internal_propagation(seeds, target_label, result_recorder);
410 return result_recorder.result();
413template<class ResultRecorder, typename Tlabel>
415yl::PropagateAlongField::
416internal_propagation(const carto::VolumeRef<Tlabel> &seeds,
417 const Tlabel target_label,
418 ResultRecorder& result_recorder) const
420 SeedAndPointAscensionResult<Tlabel> ascension_result_chooser;
422 const int size_x = seeds.getSizeX();
423 const int size_y = seeds.getSizeY();
424 const int size_z = seeds.getSizeZ();
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];
432 std::clog << "yl::PropagateAlongField::propagate_regions:\n"
433 " maximum propagation distance: "
434 << std::abs(m_step * m_max_iter) << " mm." << std::endl;
437 unsigned int n_propagated = 0, n_dead_end = 0, n_lost = 0;
440 #pragma omp parallel for schedule(dynamic)
441 for(int z = 0; z < size_z; ++z)
443 for(int y = 0; y < size_y; ++y)
444 for(int x = 0; x < size_x; ++x)
446 if(seeds(x, y, z) == target_label) {
447 const Point3df point(x * voxel_size_x,
451 std::pair<Tlabel, Point3df> result
452 = internal_ascension(point, seeds, target_label,
453 ascension_result_chooser);
454 const Tlabel& result_label = result.first;
456 if(result_label > 0) {
457 result_recorder.record(x, y, z, result);
460 } else if(result_label < 0) {
463 } else { // result_label == 0
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;
484 std::clog << "\nyl::PropagateAlongField::propagate_regions: "
485 << n_propagated << " propagated, "
486 << n_dead_end << " dead-end, "
487 << n_lost << " lost." << std::endl;