2Copyright Forschungszentrum Jülich GmbH (2018).
4Copyright Université Paris XI (2014).
6Contributor: Yann Leprince <yann.leprince@ylep.fr>.
8This file is part of highres-cortex, a collection of software designed
9to process high-resolution magnetic resonance images of the cerebral
12This software is governed by the CeCILL licence under French law and
13abiding by the rules of distribution of free software. You can use,
14modify and/or redistribute the software under the terms of the CeCILL
15licence as circulated by CEA, CNRS and INRIA at the following URL:
16<http://www.cecill.info/>.
18As a counterpart to the access to the source code and rights to copy,
19modify and redistribute granted by the licence, users are provided only
20with a limited warranty and the software's author, the holder of the
21economic rights, and the successive licensors have only limited
24In this respect, the user's attention is drawn to the risks associated
25with loading, using, modifying and/or developing or reproducing the
26software by the user in light of its specific status of scientific
27software, that may mean that it is complicated to manipulate, and that
28also therefore means that it is reserved for developers and experienced
29professionals having in-depth computer knowledge. Users are therefore
30encouraged to load and test the software's suitability as regards their
31requirements in conditions enabling the security of their systems and/or
32data to be ensured and, more generally, to use and operate it in the
33same conditions as regards security.
35The fact that you are presently reading this means that you have had
36knowledge of the CeCILL licence and that you accept its terms.
46#include <boost/heap/d_ary_heap.hpp>
47#include <boost/heap/binomial_heap.hpp>
48#include <boost/iterator/indirect_iterator.hpp>
53const int debug_output = 0;
55template <typename Tlabel> class Region;
56template <typename Tlabel>
57std::ostream& operator << (std::ostream& stream, const Region<Tlabel>& region);
59template <typename Tlabel>
62 friend std::ostream& operator << <Tlabel> (std::ostream&, const Region<Tlabel>&);
65 typedef boost::indirect_iterator<typename std::set<Region*>::const_iterator>
66 const_neighbour_iterator;
67 typedef boost::indirect_iterator<typename std::set<Region*>::iterator>
70 Region(const Tlabel region_label, const float initial_fusion_ordering)
71 : m_label(region_label), m_neighbours(), m_fusion_ordering(initial_fusion_ordering)
75 Region(const Region& other)
76 : m_label(other.m_label),
77 m_neighbours(other.m_neighbours),
78 m_fusion_ordering(other.m_fusion_ordering)
80 // Ensure reciprocity of the neighbourhood relationship
81 std::for_each(m_neighbours.begin(), m_neighbours.end(),
82 std::bind2nd(std::mem_fun(&Region::add_forward_neighbour), this));
87 // Remove this disappearing node from the neighbourhood of its neighbours
88 // (prevent broken edges in the region neighbourhood graph)
89 std::for_each(m_neighbours.begin(), m_neighbours.end(),
90 std::bind2nd(std::mem_fun(&Region::remove_forward_neighbour), this));
98 void add_neighbour(Region<Tlabel>& neighbour_region)
100 assert(&neighbour_region != this);
101 m_neighbours.insert(&neighbour_region);
102 neighbour_region.m_neighbours.insert(this);
105 float fusion_ordering() const
107 return m_fusion_ordering;
110 void update_fusion_ordering(const float new_fusion_ordering)
112 m_fusion_ordering = new_fusion_ordering;
115 const_neighbour_iterator neighbours_begin() const
116 { return m_neighbours.begin(); };
117 const_neighbour_iterator neighbours_end() const
118 { return m_neighbours.end(); };
119 neighbour_iterator neighbours_begin()
120 { return m_neighbours.begin(); };
121 neighbour_iterator neighbours_end()
122 { return m_neighbours.end(); };
124 bool operator == (const Region& other) const
126 return this == &other;
129 bool operator != (const Region& other) const
131 return this != &other;
135 void add_forward_neighbour(Region<Tlabel>* neighbour_region)
137 m_neighbours.insert(neighbour_region);
140 void remove_forward_neighbour(Region<Tlabel>* neighbour_region)
142 m_neighbours.erase(neighbour_region);
145 const Tlabel m_label;
146 std::set<Region*> m_neighbours;
147 float m_fusion_ordering;
150template <typename Tlabel, typename CacheType>
151class CachingRegion : public Region<Tlabel>
154 CachingRegion(const Tlabel region_label,
155 float initial_fusion_ordering,
156 const CacheType& cache)
157 : Region<Tlabel>(region_label, initial_fusion_ordering),
162 CachingRegion(const CachingRegion<Tlabel, CacheType>& other)
163 : Region<Tlabel>(other),
164 m_cache(other.m_cache)
168 const CacheType& cache() const { return m_cache; };
169 void set_cache(const CacheType& new_cache) { m_cache = new_cache; };
171 bool traversing() const
173 return m_cache.touches_CSF() && m_cache.touches_white();
180template <typename Tlabel>
181std::ostream& operator << (std::ostream& stream, const Region<Tlabel>& region)
183 stream << "Region@" << ®ion << " (" << "label=" << region.label()
184 << ", fusion_ordering=" << region.fusion_ordering() << ", #neighbours="
185 << region.m_neighbours.size() << ")";
189template <typename Tlabel, typename CacheType>
190class RegionInQueue : public CachingRegion<Tlabel, CacheType>
193#if defined(__APPLE__) && !defined(__clang__)
194 // d_ary_heap fails to compile on Apple GCC 4.0.1 (Mac OS 10.5), use
195 // binomial_heap instead
196 typedef boost::heap::binomial_heap<RegionInQueue<Tlabel, CacheType> > RegionQueue;
198 typedef boost::heap::d_ary_heap<RegionInQueue<Tlabel, CacheType>,
199 boost::heap::arity<8>,
200 boost::heap::mutable_<true> > RegionQueue;
203 typedef typename RegionQueue::handle_type Handle;
205 RegionInQueue(const Tlabel region_label, const float initial_fusion_ordering,
206 const CacheType& cache)
207 : CachingRegion<Tlabel, CacheType>(region_label, initial_fusion_ordering, cache),
212 RegionInQueue(const RegionInQueue<Tlabel, CacheType>& other)
213 : CachingRegion<Tlabel, CacheType>(other), m_handle(other.m_handle)
217 bool operator < (const RegionInQueue<Tlabel, CacheType>& other) const
219 // Be careful: Boost's heap is a MAX-heap (priority queue), whereas we want
220 // a MIN-heap (lower fusion_ordering gets out first). Therefore, the logic of this
221 // test is reversed: A < B means region A is of HIGHER fusion_ordering than region
223 if(this->traversing() && !other.traversing())
225 if(!this->traversing() && other.traversing())
227 return this->fusion_ordering() > other.fusion_ordering();
230 void set_handle(const Handle& handle)
235 const Handle& handle()
244}; // end of anonymous namespace
249template <typename Tlabel, class RegionQualityCriterion>
250IterativeRegionMerger<Tlabel, RegionQualityCriterion>::
251IterativeRegionMerger(const LabelVolume<Tlabel>& label_vol,
252 const RegionQualityCriterion& criterion,
254 : m_label_volume(label_vol), m_criterion(criterion),
255 m_verbosity(verbosity)
259template <typename Tlabel, class RegionQualityCriterion>
261IterativeRegionMerger<Tlabel, RegionQualityCriterion>::
262setVerbose(const int verbosity)
264 m_verbosity = verbosity;
267template <typename Tlabel, class RegionQualityCriterion>
269IterativeRegionMerger<Tlabel, RegionQualityCriterion>::
270merge_worst_regions_iteratively()
273 std::clog << "yl::IterativeRegionMerger::merge_worst_regions_iteratively:\n"
274 << " computing initial region qualities..."
278 typedef typename RegionQualityCriterion::Cache CacheType;
279 typedef RegionInQueue<Tlabel, CacheType> RegionType;
280 typedef typename RegionType::RegionQueue RegionQueue;
281 typedef typename RegionQueue::handle_type Handle;
283 // Hold the labels to retrieve them in order of increasing region fusion_ordering
285 std::map<Tlabel, Handle> label_to_handle;
287 for(typename LabelVolume<Tlabel>::const_regions_iterator
288 labels_it = m_label_volume.regions_begin(),
289 labels_end = m_label_volume.regions_end();
290 labels_it != labels_end;
293 const Tlabel label = *labels_it;
294 const CacheType cache
295 = m_criterion.cache(m_label_volume, label);
296 const float initial_fusion_ordering = m_criterion.fusion_ordering(cache);
298 Handle handle = queue.push(RegionType(label, initial_fusion_ordering, cache));
299 (*handle).set_handle(handle);
300 assert(label_to_handle.find(label) == label_to_handle.end());
301 label_to_handle[label] = handle;
305 std::clog << " " << queue.size() << " regions will be processed.\n"
306 << " filling in neighbourhoods..." << std::endl;
310 const Tlabel background_label = m_label_volume.background_label();
311 const int size_x = m_label_volume.volume().getSizeX();
312 const int size_y = m_label_volume.volume().getSizeY();
313 const int size_z = m_label_volume.volume().getSizeZ();
314 for(int z = 0; z < size_z - 1; ++z)
315 for(int y = 0; y < size_y - 1; ++y)
316 for(int x = 0; x < size_x - 1; ++x)
318 const Tlabel self_label = m_label_volume.volume().at(x, y, z);
320 // If the region was not included into the queue
321 if(label_to_handle.count(self_label) == 0)
324 if(self_label != background_label) {
325 Region<Tlabel>& self_region = *label_to_handle[self_label];
326 const Tlabel xplus_label = m_label_volume.volume().at(x + 1, y, z);
327 const Tlabel yplus_label = m_label_volume.volume().at(x, y + 1, z);
328 const Tlabel zplus_label = m_label_volume.volume().at(x, y, z + 1);
330 typename std::map<Tlabel, Handle>::const_iterator it;
331 it = label_to_handle.find(xplus_label);
332 if(it != label_to_handle.end()
333 && xplus_label != background_label && self_label != xplus_label) {
334 Region<Tlabel>& xplus_region = *(it->second);
335 self_region.add_neighbour(xplus_region);
338 it = label_to_handle.find(yplus_label);
339 if(it != label_to_handle.end()
340 && yplus_label != background_label && self_label != yplus_label) {
341 Region<Tlabel>& yplus_region = *(it->second);
342 self_region.add_neighbour(yplus_region);
345 it = label_to_handle.find(zplus_label);
346 if(it != label_to_handle.end()
347 && zplus_label != background_label && self_label != zplus_label) {
348 Region<Tlabel>& zplus_region = *(it->second);
349 self_region.add_neighbour(zplus_region);
355 unsigned int phase = 1;
357 std::clog << " iteratively merging regions..."
358 "\n Phase 1: processing non-traversing regions"
362 // Iteratively merge the worst region with one of its neighbours, until no
363 // region can be improved further by merging a neighbour.
364 while(!queue.empty())
366 const RegionType& worst_region = queue.top();
367 const bool worst_region_is_traversing = worst_region.traversing();
369 if(phase == 1 && worst_region_is_traversing) {
372 std::clog << "\n Phase 2: all regions are traversing, "
373 "merging until goal diameter" << std::endl;
376 if(m_verbosity >= 2) {
378 if(RegionQueue::constant_time_size) {
379 std::clog << queue.size() << " to go, ";
381 std::clog << m_label_volume.n_regions() << " regions, size = "
382 << worst_region.fusion_ordering()
383 << ", pseudo_area = " << m_criterion.pseudo_area(worst_region.cache());
384 // TODO clear line more elegantly
385 std::clog << " \r" << std::flush;
388 if(!worst_region_is_traversing
389 || m_criterion.want_fusion(worst_region.cache())) {
390 RegionType* best_neighbour_region_p = 0;
391 CacheType best_cache;
392 float min_pseudo_area;
394 for(typename Region<Tlabel>::neighbour_iterator
395 neighbour_it = worst_region.neighbours_begin(),
396 neighbour_end = worst_region.neighbours_end();
397 neighbour_it != neighbour_end;
400 RegionType& neighbour_region = dynamic_cast<RegionType&>(*neighbour_it);
401 const CacheType conjunction_cache
402 = worst_region.cache() + neighbour_region.cache();
403 const float conjunction_pseudo_area
404 = m_criterion.pseudo_area(conjunction_cache);
405 if(!best_neighbour_region_p
406 || conjunction_pseudo_area < min_pseudo_area) {
407 min_pseudo_area = conjunction_pseudo_area;
408 best_cache = conjunction_cache;
409 best_neighbour_region_p = &neighbour_region;
413 if(!best_neighbour_region_p) {
414 // The region has no neighbours, discard it
415 if(m_verbosity >= 3) {
416 std::clog << "\n region " << worst_region
417 << " has no neighbours, discarding it." << std::endl;
421 // The region is merged with its best candidate neighbour
422 RegionType& best_neighbour_region = *best_neighbour_region_p;
423 if(m_verbosity >= 4) {
424 std::clog << "\n merging with best neighbour "
425 << best_neighbour_region
426 << " (pseudo_area = " << min_pseudo_area << ")"
430 // worst_region is eaten by its best_neighbour_region
431 m_label_volume.merge_regions(best_neighbour_region.label(),
432 worst_region.label());
434 best_neighbour_region.set_cache(best_cache);
436 // Update new region's neighbourhood
437 for(typename Region<Tlabel>::neighbour_iterator
438 neighbour_it = worst_region.neighbours_begin(),
439 neighbour_end = worst_region.neighbours_end();
440 neighbour_it != neighbour_end;
443 Region<Tlabel>& neighbour_region = *neighbour_it;
444 if(neighbour_region != best_neighbour_region) {
445 best_neighbour_region.add_neighbour(neighbour_region);
449 // Get rid of worst_region. This needs to be done *before* updating the
453 const float new_fusion_ordering
454 = m_criterion.fusion_ordering(best_neighbour_region.cache());
455 assert(best_neighbour_region.fusion_ordering() < new_fusion_ordering);
456 best_neighbour_region.update_fusion_ordering(new_fusion_ordering);
457 queue.decrease(best_neighbour_region.handle());
460 // This case can only be reached when worst_region.traversing() is true
461 // (see previous case). All regions are guaranteed to be traversing if
462 // worst_region is so, because the ordering criterion puts all traversing
463 // region above non-traversing regions. Thus, when all regions are
464 // traversing then we can begin to drop regions (i.e. consider them
465 // definitive). If we dropped regions before that, it could prevent
466 // adjacent non-traversing regions from merging.
467 assert(worst_region_is_traversing);
468 if(m_verbosity >= 3) {
469 std::clog << "\n region " << worst_region
470 << " cannot be improved by merging a neighbour" << std::endl;
475 if(m_verbosity >= 1) {
476 std::clog << "end: " << m_label_volume.n_regions() << " regions." << std::endl;