highres-cortex 6.0.4
iterative_region_merger.tcc
Go to the documentation of this file.
1/*
2Copyright Forschungszentrum Jülich GmbH (2018).
3Copyright CEA (2014).
4Copyright Université Paris XI (2014).
5
6Contributor: Yann Leprince <yann.leprince@ylep.fr>.
7
8This file is part of highres-cortex, a collection of software designed
9to process high-resolution magnetic resonance images of the cerebral
10cortex.
11
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/>.
17
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
22liability.
23
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.
34
35The fact that you are presently reading this means that you have had
36knowledge of the CeCILL licence and that you accept its terms.
37*/
38
39#include <cassert>
40#include <algorithm>
41#include <functional>
42#include <iostream>
43#include <iterator>
44#include <set>
45
46#include <boost/heap/d_ary_heap.hpp>
47#include <boost/heap/binomial_heap.hpp>
48#include <boost/iterator/indirect_iterator.hpp>
49
50namespace
51{
52
53const int debug_output = 0;
54
55template <typename Tlabel> class Region;
56template <typename Tlabel>
57std::ostream& operator << (std::ostream& stream, const Region<Tlabel>& region);
58
59template <typename Tlabel>
60class Region
61{
62 friend std::ostream& operator << <Tlabel> (std::ostream&, const Region<Tlabel>&);
63
64public:
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>
68 neighbour_iterator;
69
70 Region(const Tlabel region_label, const float initial_fusion_ordering)
71 : m_label(region_label), m_neighbours(), m_fusion_ordering(initial_fusion_ordering)
72 {
73 }
74
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)
79 {
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));
83 }
84
85 virtual ~Region()
86 {
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));
91 }
92
93 Tlabel label() const
94 {
95 return m_label;
96 }
97
98 void add_neighbour(Region<Tlabel>& neighbour_region)
99 {
100 assert(&neighbour_region != this);
101 m_neighbours.insert(&neighbour_region);
102 neighbour_region.m_neighbours.insert(this);
103 }
104
105 float fusion_ordering() const
106 {
107 return m_fusion_ordering;
108 }
109
110 void update_fusion_ordering(const float new_fusion_ordering)
111 {
112 m_fusion_ordering = new_fusion_ordering;
113 }
114
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(); };
123
124 bool operator == (const Region& other) const
125 {
126 return this == &other;
127 }
128
129 bool operator != (const Region& other) const
130 {
131 return this != &other;
132 }
133
134private:
135 void add_forward_neighbour(Region<Tlabel>* neighbour_region)
136 {
137 m_neighbours.insert(neighbour_region);
138 }
139
140 void remove_forward_neighbour(Region<Tlabel>* neighbour_region)
141 {
142 m_neighbours.erase(neighbour_region);
143 }
144
145 const Tlabel m_label;
146 std::set<Region*> m_neighbours;
147 float m_fusion_ordering;
148};
149
150template <typename Tlabel, typename CacheType>
151class CachingRegion : public Region<Tlabel>
152{
153public:
154 CachingRegion(const Tlabel region_label,
155 float initial_fusion_ordering,
156 const CacheType& cache)
157 : Region<Tlabel>(region_label, initial_fusion_ordering),
158 m_cache(cache)
159 {
160 }
161
162 CachingRegion(const CachingRegion<Tlabel, CacheType>& other)
163 : Region<Tlabel>(other),
164 m_cache(other.m_cache)
165 {
166 }
167
168 const CacheType& cache() const { return m_cache; };
169 void set_cache(const CacheType& new_cache) { m_cache = new_cache; };
170
171 bool traversing() const
172 {
173 return m_cache.touches_CSF() && m_cache.touches_white();
174 };
175
176private:
177 CacheType m_cache;
178};
179
180template <typename Tlabel>
181std::ostream& operator << (std::ostream& stream, const Region<Tlabel>& region)
182{
183 stream << "Region@" << &region << " (" << "label=" << region.label()
184 << ", fusion_ordering=" << region.fusion_ordering() << ", #neighbours="
185 << region.m_neighbours.size() << ")";
186 return stream;
187}
188
189template <typename Tlabel, typename CacheType>
190class RegionInQueue : public CachingRegion<Tlabel, CacheType>
191{
192public:
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;
197#else
198 typedef boost::heap::d_ary_heap<RegionInQueue<Tlabel, CacheType>,
199 boost::heap::arity<8>,
200 boost::heap::mutable_<true> > RegionQueue;
201#endif
202
203 typedef typename RegionQueue::handle_type Handle;
204
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),
208 m_handle()
209 {
210 }
211
212 RegionInQueue(const RegionInQueue<Tlabel, CacheType>& other)
213 : CachingRegion<Tlabel, CacheType>(other), m_handle(other.m_handle)
214 {
215 }
216
217 bool operator < (const RegionInQueue<Tlabel, CacheType>& other) const
218 {
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
222 // B.
223 if(this->traversing() && !other.traversing())
224 return true;
225 if(!this->traversing() && other.traversing())
226 return false;
227 return this->fusion_ordering() > other.fusion_ordering();
228 }
229
230 void set_handle(const Handle& handle)
231 {
232 m_handle = handle;
233 }
234
235 const Handle& handle()
236 {
237 return m_handle;
238 }
239
240private:
241 Handle m_handle;
242};
243
244}; // end of anonymous namespace
245
246namespace yl
247{
248
249template <typename Tlabel, class RegionQualityCriterion>
250IterativeRegionMerger<Tlabel, RegionQualityCriterion>::
251IterativeRegionMerger(const LabelVolume<Tlabel>& label_vol,
252 const RegionQualityCriterion& criterion,
253 const int verbosity)
254 : m_label_volume(label_vol), m_criterion(criterion),
255 m_verbosity(verbosity)
256{
257}
258
259template <typename Tlabel, class RegionQualityCriterion>
260void
261IterativeRegionMerger<Tlabel, RegionQualityCriterion>::
262setVerbose(const int verbosity)
263{
264 m_verbosity = verbosity;
265}
266
267template <typename Tlabel, class RegionQualityCriterion>
268void
269IterativeRegionMerger<Tlabel, RegionQualityCriterion>::
270merge_worst_regions_iteratively()
271{
272 if(m_verbosity) {
273 std::clog << "yl::IterativeRegionMerger::merge_worst_regions_iteratively:\n"
274 << " computing initial region qualities..."
275 << std::endl;
276 }
277
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;
282
283 // Hold the labels to retrieve them in order of increasing region fusion_ordering
284 RegionQueue queue;
285 std::map<Tlabel, Handle> label_to_handle;
286
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;
291 ++labels_it)
292 {
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);
297
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;
302 }
303
304 if(m_verbosity) {
305 std::clog << " " << queue.size() << " regions will be processed.\n"
306 << " filling in neighbourhoods..." << std::endl;
307 }
308
309 {
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)
317 {
318 const Tlabel self_label = m_label_volume.volume().at(x, y, z);
319
320 // If the region was not included into the queue
321 if(label_to_handle.count(self_label) == 0)
322 continue;
323
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);
329
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);
336 }
337
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);
343 }
344
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);
350 }
351 }
352 }
353 }
354
355 unsigned int phase = 1;
356 if(m_verbosity) {
357 std::clog << " iteratively merging regions..."
358 "\n Phase 1: processing non-traversing regions"
359 << std::endl;
360 }
361
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())
365 {
366 const RegionType& worst_region = queue.top();
367 const bool worst_region_is_traversing = worst_region.traversing();
368
369 if(phase == 1 && worst_region_is_traversing) {
370 phase = 2;
371 if(m_verbosity)
372 std::clog << "\n Phase 2: all regions are traversing, "
373 "merging until goal diameter" << std::endl;
374 }
375
376 if(m_verbosity >= 2) {
377 std::clog << " ";
378 if(RegionQueue::constant_time_size) {
379 std::clog << queue.size() << " to go, ";
380 }
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;
386 }
387
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;
393
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;
398 ++neighbour_it)
399 {
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;
410 }
411 }
412
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;
418 }
419 queue.pop();
420 } else {
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 << ")"
427 << std::endl;
428 }
429
430 // worst_region is eaten by its best_neighbour_region
431 m_label_volume.merge_regions(best_neighbour_region.label(),
432 worst_region.label());
433
434 best_neighbour_region.set_cache(best_cache);
435
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;
441 ++neighbour_it)
442 {
443 Region<Tlabel>& neighbour_region = *neighbour_it;
444 if(neighbour_region != best_neighbour_region) {
445 best_neighbour_region.add_neighbour(neighbour_region);
446 }
447 }
448
449 // Get rid of worst_region. This needs to be done *before* updating the
450 // heap!
451 queue.pop();
452
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());
458 }
459 } else {
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;
471 }
472 queue.pop();
473 }
474 }
475 if(m_verbosity >= 1) {
476 std::clog << "end: " << m_label_volume.n_regions() << " regions." << std::endl;
477 }
478}
479
480} // namespace yl