GooseEYE 0.9.1
Loading...
Searching...
No Matches
GooseEYE.h
Go to the documentation of this file.
1
7#ifndef GOOSEEYE_H
8#define GOOSEEYE_H
9
10#include "config.h"
11#include "detail.hpp"
12#include "version.h"
13#include <prrng.h>
14#include <xtensor/xstrided_view.hpp>
15
16namespace GooseEYE {
17
18namespace detail {
19
23inline std::string get_namespace()
24{
25 std::string ret = "GooseEYE";
26#ifdef GOOSEEYE_USE_XTENSOR_PYTHON
27 return ret + ".";
28#else
29 return ret + "::";
30#endif
31}
32
37template <class T>
38inline std::string shape_to_string(const T& shape)
39{
40 std::string ret = "[";
41 for (size_t i = 0; i < shape.size(); ++i) {
42 ret += std::to_string(shape[i]);
43 if (i < shape.size() - 1) {
44 ret += ", ";
45 }
46 }
47 ret += "]";
48 return ret;
49}
50
51} // namespace detail
52
56namespace kernel {
57
64{
65 GOOSEEYE_ASSERT(ndim > 0 && ndim <= 3, std::out_of_range);
66
67 std::vector<size_t> shape(ndim, 3);
68
69 array_type::array<int> kern = xt::zeros<int>(shape);
70
71 if (ndim == 1) {
72 xt::view(kern, xt::all()) = 1;
73 return kern;
74 }
75
76 if (ndim == 2) {
77 xt::view(kern, xt::keep(1), xt::all()) = 1;
78 xt::view(kern, xt::all(), xt::keep(1)) = 1;
79 return kern;
80 }
81
82 xt::view(kern, xt::keep(1), xt::all(), xt::all()) = 1;
83 xt::view(kern, xt::all(), xt::keep(1), xt::all()) = 1;
84 xt::view(kern, xt::all(), xt::all(), xt::keep(1)) = 1;
85 return kern;
86}
87
88} // namespace kernel
89
93enum class path_mode {
94 Bresenham,
95 actual,
96 full
97};
98
110
121 const std::vector<size_t>& shape,
125 bool periodic = true)
126{
127 GOOSEEYE_ASSERT(row.shape() == col.shape(), std::out_of_range);
128 GOOSEEYE_ASSERT(row.shape() == r.shape(), std::out_of_range);
129 GOOSEEYE_ASSERT(shape.size() == 2, std::out_of_range);
130
131 array_type::array<int> out = xt::zeros<int>(shape);
132
133 if (periodic) {
134 for (size_t i = 0; i < row.size(); ++i) {
135 for (int di = -r(i); di <= r(i); ++di) {
136 for (int dj = -r(i); dj <= r(i); ++dj) {
137 int dr = (int)(ceil(pow((double)(pow(di, 2) + pow(dj, 2)), 0.5)));
138 if (dr < r(i)) {
139 out.periodic(row(i) + di, col(i) + dj) = 1;
140 }
141 }
142 }
143 }
144
145 return out;
146 }
147
148 for (size_t i = 0; i < row.size(); ++i) {
149 for (int di = -r(i); di <= r(i); ++di) {
150 for (int dj = -r(i); dj <= r(i); ++dj) {
151 if (out.in_bounds(row(i) + di, col(i) + dj)) {
152 int dr = (int)(ceil(pow((double)(pow(di, 2) + pow(dj, 2)), 0.5)));
153 if (dr < r(i)) {
154 out(row(i) + di, col(i) + dj) = 1;
155 }
156 }
157 }
158 }
159 }
160
161 return out;
162}
163
172dummy_circles(const std::vector<size_t>& shape, bool periodic = true, uint64_t seed = 0)
173{
174 GOOSEEYE_ASSERT(shape.size() == 2, std::out_of_range);
175 prrng::pcg32 rng(seed);
176
177 // set default: number of circles in both directions and (constant) radius
178 size_t N = (size_t)(0.05 * (double)shape[0]);
179 size_t M = (size_t)(0.05 * (double)shape[1]);
180 size_t R = (size_t)(pow((0.3 * (double)(shape[0] * shape[1])) / (M_PI * (double)(N * M)), 0.5));
181
182 array_type::tensor<int, 1> row = xt::empty<int>({M * N});
183 array_type::tensor<int, 1> col = xt::empty<int>({M * N});
184 array_type::tensor<int, 1> r = xt::empty<int>({M * N});
185
186 // define regular grid of circles
187 for (size_t i = 0; i < N; i++) {
188 for (size_t j = 0; j < M; j++) {
189 row[i * M + j] = (int)((double)i * (double)shape[0] / (double)N);
190 col[i * M + j] = (int)((double)j * (double)shape[1] / (double)M);
191 r[i * M + j] = (int)R;
192 }
193 }
194
195 // distance between circles
196 int dN = (int)(0.5 * (double)shape[0] / (double)N);
197 int dM = (int)(0.5 * (double)shape[1] / (double)M);
198
199 // randomly perturb circles (move in any direction, enlarge/shrink)
200 for (size_t i = 0; i < N * M; i++) {
201 row(i) += rng.randint(2 * dN) - dN;
202 col(i) += rng.randint(2 * dM) - dM;
203 r(i) = (double)r(i) * 2.0 * rng.random();
204 }
205
206 // convert to image
207 return dummy_circles(shape, row, col, r, periodic);
208}
209
210namespace detail {
211
212template <class S, class U>
213inline void periodic_copy_below_to_above(S&& F, const U& Pad)
214{
215 xt::xstrided_slice_vector sbelow(F.dimension());
216 xt::xstrided_slice_vector sabove(F.dimension());
217
218 for (size_t ax = 0; ax < F.dimension(); ++ax) {
219 if (F.shape(ax) <= 1) {
220 continue;
221 }
222
223 for (size_t i = 0; i < ax; ++i) {
224 sbelow[i] = xt::all();
225 sabove[i] = xt::all();
226 }
227
228 sbelow[ax] = xt::range(0, Pad[ax][0]);
229 sabove[ax] = xt::range(F.shape(ax) - Pad[ax][1] - Pad[ax][0], F.shape(ax) - Pad[ax][1]);
230
231 for (size_t i = ax + 1; i < F.dimension(); ++i) {
232 sbelow[i] = xt::all();
233 sabove[i] = xt::all();
234 }
235
236 auto below = xt::strided_view(F, sbelow);
237 auto above = xt::strided_view(F, sabove);
238
239 above = xt::where(xt::not_equal(below, 0), below, above);
240 }
241}
242
243template <class S, class U>
244inline void periodic_copy_above_to_below(S&& F, const U& Pad)
245{
246 xt::xstrided_slice_vector sbelow(F.dimension());
247 xt::xstrided_slice_vector sabove(F.dimension());
248
249 for (size_t ax = 0; ax < F.dimension(); ++ax) {
250 if (F.shape(ax) <= 1) {
251 continue;
252 }
253
254 for (size_t i = 0; i < ax; ++i) {
255 sbelow[i] = xt::all();
256 sabove[i] = xt::all();
257 }
258
259 sbelow[ax] = xt::range(Pad[ax][0], Pad[ax][0] + Pad[ax][1]);
260 sabove[ax] = xt::range(F.shape(ax) - Pad[ax][1], F.shape(ax));
261
262 for (size_t i = ax + 1; i < F.dimension(); ++i) {
263 sbelow[i] = xt::all();
264 sabove[i] = xt::all();
265 }
266
267 auto below = xt::strided_view(F, sbelow);
268 auto above = xt::strided_view(F, sabove);
269
270 below = xt::where(xt::not_equal(above, 0), above, below);
271 }
272}
273
274template <class S, class U>
275inline void periodic_copy(S&& F, const U& Pad)
276{
277 periodic_copy_above_to_below(F, Pad);
278 periodic_copy_below_to_above(F, Pad);
279}
280
281} // namespace detail
282
292template <
293 class T,
294 class S,
295 std::enable_if_t<
296 std::is_integral<typename T::value_type>::value &&
297 std::is_integral<typename S::value_type>::value,
298 int> = 0>
299inline T dilate(
300 const T& f,
301 const S& kernel,
302 const array_type::tensor<size_t, 1>& iterations,
303 bool periodic = true)
304{
305 using value_type = typename T::value_type;
306 GOOSEEYE_ASSERT(f.dimension() <= 3, std::out_of_range);
307 GOOSEEYE_ASSERT(f.dimension() == kernel.dimension(), std::out_of_range);
308 GOOSEEYE_ASSERT(xt::all(xt::equal(kernel, 0) || xt::equal(kernel, 1)), std::out_of_range);
309 GOOSEEYE_ASSERT(static_cast<size_t>(xt::amax(f)()) <= iterations.size() + 1, std::out_of_range);
310
311 xt::pad_mode pad_mode = xt::pad_mode::constant;
312 int pad_value = 0;
313
314 if (periodic) {
315 pad_mode = xt::pad_mode::periodic;
316 }
317
318 array_type::tensor<typename S::value_type, 3> K = xt::atleast_3d(kernel);
319 auto Pad = detail::pad_width(K);
320 array_type::tensor<value_type, 3> F = xt::pad(xt::atleast_3d(f), Pad, pad_mode, pad_value);
321 array_type::tensor<value_type, 3> G = F; // copy to list added labels added in the iteration
322
323 for (size_t iter = 0; iter < xt::amax(iterations)(0); ++iter) {
324
325 for (size_t h = Pad[0][0]; h < F.shape(0) - Pad[0][1]; ++h) {
326 for (size_t i = Pad[1][0]; i < F.shape(1) - Pad[1][1]; ++i) {
327 for (size_t j = Pad[2][0]; j < F.shape(2) - Pad[2][1]; ++j) {
328
329 // get label
330 value_type l = F(h, i, j);
331
332 // skip if needed:
333 // - do not dilate background or labels added in this iteration
334 // - check the maximum number of iterations per label
335 if (l == 0) {
336 continue;
337 }
338 if (iter >= iterations(l)) {
339 continue;
340 }
341
342 // get sub-matrix around (h, i, j)
343 auto Fi = xt::view(
344 F,
345 xt::range(h - Pad[0][0], h + Pad[0][1] + 1),
346 xt::range(i - Pad[1][0], i + Pad[1][1] + 1),
347 xt::range(j - Pad[2][0], j + Pad[2][1] + 1));
348
349 auto Gi = xt::view(
350 G,
351 xt::range(h - Pad[0][0], h + Pad[0][1] + 1),
352 xt::range(i - Pad[1][0], i + Pad[1][1] + 1),
353 xt::range(j - Pad[2][0], j + Pad[2][1] + 1));
354
355 // dilate where needed: dilated labels are added as negative numbers
356 Gi = xt::where(xt::equal(Fi, 0) && xt::equal(K, 1), l, Gi);
357 }
358 }
359 }
360
361 // apply periodicity
362 if (periodic) {
363 detail::periodic_copy(G, Pad);
364 }
365
366 // flip added label
367 F = G;
368 G = F;
369 }
370
371 // remove padding
372 F = xt::view(
373 F,
374 xt::range(Pad[0][0], F.shape(0) - Pad[0][1]),
375 xt::range(Pad[1][0], F.shape(1) - Pad[1][1]),
376 xt::range(Pad[2][0], F.shape(2) - Pad[2][1]));
377
378 T ret = f;
379 std::copy(F.cbegin(), F.cend(), ret.begin());
380 return ret;
381}
382
387template <class T, std::enable_if_t<std::is_integral<typename T::value_type>::value, int> = 0>
388inline T dilate(const T& f, const array_type::tensor<size_t, 1>& iterations, bool periodic = true)
389{
390 return dilate(f, kernel::nearest(f.dimension()), iterations, periodic);
391}
392
397template <
398 class T,
399 class S,
400 std::enable_if_t<
401 std::is_integral<typename T::value_type>::value &&
402 std::is_integral<typename S::value_type>::value,
403 int> = 0>
404inline T dilate(const T& f, const S& kernel, size_t iterations = 1, bool periodic = true)
405{
406 array_type::tensor<size_t, 1> iter = iterations * xt::ones<size_t>({xt::amax(f)(0) + 1ul});
407 return dilate(f, kernel, iter, periodic);
408}
409
414template <class T, std::enable_if_t<std::is_integral<typename T::value_type>::value, int> = 0>
415inline T dilate(const T& f, size_t iterations = 1, bool periodic = true)
416{
417 array_type::tensor<size_t, 1> iter = iterations * xt::ones<size_t>({xt::amax(f)(0) + 1ul});
418 return dilate(f, kernel::nearest(f.dimension()), iter, periodic);
419}
420
427template <class T>
429{
430 using value_type = typename T::value_type;
431 std::map<value_type, value_type> map;
432
433 for (size_t i = 0; i < a.size(); ++i) {
434 map.try_emplace(a.flat(i), b.flat(i));
435 }
436
437 size_t i = 0;
439 xt::empty<typename T::value_type>(std::array<size_t, 2>{map.size(), 2});
440
441 for (auto const& [key, val] : map) {
442 ret(i, 0) = key;
443 ret(i, 1) = val;
444 ++i;
445 }
446
447 return ret;
448}
449
456template <class L, class A>
457inline L labels_rename(const L& labels, const A& rename)
458{
459 GOOSEEYE_ASSERT(rename.dimension() == 2, std::out_of_range);
460 GOOSEEYE_ASSERT(rename.shape(1) == 2, std::out_of_range);
461 using value_type = typename A::value_type;
462 std::map<value_type, value_type> map;
463 for (size_t i = 0; i < rename.shape(0); ++i) {
464 map.emplace(rename(i, 0), rename(i, 1));
465 }
466
467#ifdef GOOSEEYE_ENABLE_ASSERT
468 auto l = xt::unique(labels);
469 for (size_t i = 0; i < l.size(); ++i) {
470 GOOSEEYE_ASSERT(map.count(l(i)) > 0, std::out_of_range);
471 }
472#endif
473
474 L ret = xt::empty_like(labels);
475 for (size_t i = 0; i < labels.size(); ++i) {
476 ret.flat(i) = map[labels.flat(i)];
477 }
478
479 return ret;
480}
481
489template <class T>
490inline T labels_prune(const T& labels)
491{
492 using value_type = typename T::value_type;
493 auto unq = xt::unique(labels);
494 bool background = xt::any(xt::equal(unq, 0));
495
496 std::array<size_t, 2> shape = {unq.size(), 2};
497 array_type::tensor<value_type, 2> rename = xt::empty<value_type>(shape);
498
499 if (background) {
500 rename(0, 0) = 0;
501 rename(0, 1) = 0;
502 if (unq(0) == 0) {
503 for (size_t i = 1; i < unq.size(); ++i) {
504 rename(i, 0) = unq(i);
505 rename(i, 1) = i;
506 }
507 }
508 else {
509 size_t row = 1;
510 for (size_t i = 0; i < unq.size(); ++i) {
511 if (unq(i) == 0) {
512 continue;
513 }
514 rename(row, 0) = unq(i);
515 rename(row, 1) = row;
516 row++;
517 }
518 }
519 }
520 else {
521 for (size_t i = 0; i < unq.size(); ++i) {
522 rename(i, 0) = unq(i);
523 rename(i, 1) = i + 1;
524 }
525 }
526 return labels_rename(labels, rename);
527}
528
535template <class L, class A>
536inline L labels_reorder(const L& labels, const A& order)
537{
538#ifdef GOOSEEYE_ENABLE_ASSERT
539 auto a = xt::unique(labels);
540 auto b = xt::unique(order);
541 GOOSEEYE_ASSERT(a.size() == b.size(), std::out_of_range);
542 GOOSEEYE_ASSERT(xt::all(xt::equal(a, b)), std::out_of_range);
543#endif
544
545 auto maxlab = *std::max_element(order.begin(), order.end());
546 std::vector<typename A::value_type> renum(maxlab + 1);
547
548 for (size_t i = 0; i < order.size(); ++i) {
549 renum[order[i]] = i;
550 }
551
552 L ret = xt::empty_like(labels);
553 for (size_t i = 0; i < labels.size(); ++i) {
554 ret.flat(i) = renum[labels.flat(i)];
555 }
556
557 return ret;
558}
559
560namespace detail {
561
562template <class T>
563inline auto labels_sizes_impl(const T& labels)
564{
565 using value_type = typename T::value_type;
566 std::map<value_type, value_type> map;
567
568 for (size_t i = 0; i < labels.size(); ++i) {
569 if (map.count(labels.flat(i)) == 0) {
570 map.emplace(labels.flat(i), 1);
571 }
572 else {
573 map[labels.flat(i)]++;
574 }
575 }
576
577 return map;
578}
579
580} // namespace detail
581
587template <class T>
589{
590 using value_type = typename T::value_type;
591 auto map = detail::labels_sizes_impl(labels);
592 std::array<size_t, 2> shape = {map.size(), 2};
593 array_type::tensor<value_type, 2> ret = xt::empty<value_type>(shape);
594 size_t i = 0;
595
596 for (auto const& [key, val] : map) {
597 ret(i, 0) = key;
598 ret(i, 1) = val;
599 ++i;
600 }
601
602 return ret;
603}
604
611template <class T, class N>
612inline array_type::tensor<typename T::value_type, 1> labels_sizes(const T& labels, const N& names)
613{
614 using value_type = typename T::value_type;
615 auto map = detail::labels_sizes_impl(labels);
616 array_type::tensor<value_type, 1> ret = xt::zeros<value_type>({names.size()});
617 for (size_t i = 0; i < names.size(); ++i) {
618 ret(i) = map[names(i)];
619 }
620 return ret;
621}
622
623namespace detail {
624
630template <size_t Dim, class T>
631inline array_type::tensor<ptrdiff_t, 2> kernel_to_dx(T kernel)
632{
633#ifdef GOOSEEYE_ENABLE_ASSERT
634 for (size_t i = 0; i < Dim; ++i) {
635 GOOSEEYE_ASSERT(kernel.shape(i) % 2 == 1, std::out_of_range);
636 }
637#endif
638
639 std::array<size_t, Dim> mid;
640 for (size_t i = 0; i < Dim; ++i) {
641 mid[i] = (kernel.shape(i) - 1) / 2;
642 }
643 size_t idx = 0;
644 for (size_t i = 0; i < Dim; ++i) {
645 idx += mid[i] * kernel.strides()[i];
646 }
647 GOOSEEYE_ASSERT(kernel.flat(idx) == 1, std::out_of_range);
648 kernel.flat(idx) = 0;
649
650 if constexpr (Dim == 1) {
651 auto i = xt::flatten_indices(xt::argwhere(kernel)) - mid[0];
652 array_type::tensor<ptrdiff_t, 2> ret = xt::empty<ptrdiff_t>({i.size(), size_t(1)});
653 std::copy(i.begin(), i.end(), ret.begin());
654 return ret;
655 }
656
657 auto ret = xt::from_indices(xt::argwhere(kernel));
658 for (size_t i = 0; i < Dim; ++i) {
659 xt::view(ret, xt::all(), i) -= mid[i];
660 }
661 return ret;
662}
663
664} // namespace detail
665
671template <size_t Dimension, bool Periodicity = true>
673public:
674 static constexpr size_t Dim = Dimension;
675 static constexpr bool Periodic = Periodicity;
676
677private:
678 std::array<ptrdiff_t, Dim> m_shape;
681 ptrdiff_t m_new_label = 1;
682 size_t m_nmerge = 0;
683 std::array<ptrdiff_t, Dim> m_strides;
684
691 std::vector<ptrdiff_t> m_renum;
692
703 std::vector<ptrdiff_t> m_next;
704 std::vector<ptrdiff_t> m_connected;
705
706 typedef ptrdiff_t (ClusterLabeller<Dimension, Periodicity>::*CompareImpl)(size_t, size_t);
708
709public:
713 template <class T>
715 {
716 if constexpr (Dim == 1) {
717 // kernel = {1, 1, 1}
718 m_dx = {{-1}, {1}};
719 }
720 else if constexpr (Dim == 2) {
721 // kernel = {{0, 1, 0}, {1, 1, 1}, {0, 1, 0}};
722 m_dx = {{-1, 0}, {0, -1}, {0, 1}, {1, 0}};
723 }
724 else if constexpr (Dim == 3) {
725 m_dx = {{-1, 0, 0}, {0, -1, 0}, {0, 0, -1}, {0, 0, 1}, {0, 1, 0}, {1, 0, 0}};
726 }
727 else {
728 throw std::runtime_error("Please specify the kernel in dimensions > 3.");
729 }
730 this->init(shape);
731 }
732
737 template <class T, class K>
738 ClusterLabeller(const T& shape, const K& kernel)
739 {
740 m_dx = detail::kernel_to_dx<Dim>(kernel);
741 this->init(shape);
742 }
743
744private:
745 template <class T>
746 void init(const T& shape)
747 {
748 m_label = xt::empty<ptrdiff_t>(shape);
749 m_renum.resize(m_label.size() + 1);
750 m_next.resize(m_label.size() + 1);
751 for (size_t i = 0; i < Dim; ++i) {
752 m_shape[i] = static_cast<ptrdiff_t>(shape[i]);
753 if constexpr (Dim >= 2) {
754 m_strides[i] = static_cast<ptrdiff_t>(m_label.strides()[i]);
755 }
756 }
757 this->reset();
758 m_connected.resize(m_dx.shape(0));
759
760 // Dim == 2: by default strides are assumed non-zero to avoid extra checks
761 // check once if zeros strides occur and if so use a special implementation of unravel_index
762 if constexpr (Dim == 2) {
763 if (m_shape[0] == 1) {
764 get_compare = &ClusterLabeller<Dimension, Periodicity>::get_compare_2d_1n;
765 }
766 else if (m_shape[1] == 1) {
767 get_compare = &ClusterLabeller<Dimension, Periodicity>::get_compare_2d_n1;
768 }
769 }
770 }
771
772public:
776 void reset()
777 {
778 std::fill(m_label.begin(), m_label.end(), 0);
779 std::iota(m_renum.begin(), m_renum.end(), 0);
780 m_new_label = 1;
781 this->clean_next();
782 }
783
788 void prune()
789 {
790 ptrdiff_t n = static_cast<ptrdiff_t>(m_new_label);
791 m_new_label = 1;
792 m_renum[0] = 0;
793 for (ptrdiff_t i = 1; i < n; ++i) {
794 if (m_renum[i] == i) {
795 m_renum[i] = m_new_label;
796 ++m_new_label;
797 }
798 }
799 this->private_renumber(m_renum);
800 std::iota(m_renum.begin(), m_renum.begin() + n, 0);
801 }
802
803private:
807 void clean_next()
808 {
809 std::fill(m_next.begin(), m_next.end(), -1);
810 }
811
816 template <class T>
817 void private_renumber(const T& renum)
818 {
819 for (size_t i = 0; i < m_label.size(); ++i) {
820 m_label.flat(i) = renum[m_label.flat(i)];
821 }
822 }
823
832 void merge_detail(ptrdiff_t a, ptrdiff_t b)
833 {
834 // -> head[list(b)] = head[a]
835 ptrdiff_t i = m_renum[b];
836 ptrdiff_t target = m_renum[a];
837 m_renum[b] = target;
838 while (true) {
839 i = m_next[i];
840 if (i == -1) {
841 break;
842 }
843 m_renum[i] = target;
844 }
845 // -> list(head[a]).append(list(b))
846 while (m_next[a] != -1) {
847 a = m_next[a];
848 }
849 m_next[a] = b;
850 }
851
858 size_t unique(ptrdiff_t* labels, size_t nlabels)
859 {
860 std::sort(labels, labels + nlabels);
861 return std::unique(labels, labels + nlabels) - labels;
862 }
863
871 ptrdiff_t merge(ptrdiff_t* labels, size_t nlabels)
872 {
873 ptrdiff_t target = labels[0];
874 for (size_t i = 1; i < nlabels; ++i) {
875 this->merge_detail(target, labels[i]);
876 }
877 return target;
878 }
879
880 void apply_merge()
881 {
882 if (m_nmerge == 0) {
883 return;
884 }
885
886 this->private_renumber(m_renum);
887 this->clean_next();
888 m_nmerge = 0;
889 }
890
895 ptrdiff_t get_compare_2d_1n(size_t idx, size_t j)
896 {
897 if constexpr (Periodic) {
898 return (m_shape[1] + idx + m_dx(j, 1)) % m_shape[1];
899 }
900 if constexpr (!Periodic) {
901 ptrdiff_t compare = idx + m_dx(j, 1);
902 if (compare < 0 || compare >= m_shape[1]) {
903 return -1;
904 }
905 return compare;
906 }
907 }
908
913 ptrdiff_t get_compare_2d_n1(size_t idx, size_t j)
914 {
915 if constexpr (Periodic) {
916 return (m_shape[0] + idx + m_dx(j, 0)) % m_shape[0];
917 }
918 if constexpr (!Periodic) {
919 ptrdiff_t compare = idx + m_dx(j, 0);
920 if (compare < 0 || compare >= m_shape[0]) {
921 return -1;
922 }
923 return compare;
924 }
925 }
926
935 ptrdiff_t get_compare_default(size_t idx, size_t j)
936 {
937 if constexpr (Dim == 1 && Periodic) {
938 return (m_shape[0] + idx + m_dx.flat(j)) % m_shape[0];
939 }
940 if constexpr (Dim == 1 && !Periodic) {
941 ptrdiff_t compare = idx + m_dx.flat(j);
942 if (compare < 0 || compare >= m_shape[0]) {
943 return -1;
944 }
945 return idx + m_dx.flat(j);
946 }
947 if constexpr (Dim == 2 && Periodic) {
948 ptrdiff_t ii = (m_shape[0] + (idx / m_strides[0]) + m_dx(j, 0)) % m_shape[0];
949 ptrdiff_t jj = (m_shape[1] + (idx % m_strides[0]) + m_dx(j, 1)) % m_shape[1];
950 return ii * m_shape[1] + jj;
951 }
952 if constexpr (Dim == 2 && !Periodic) {
953 ptrdiff_t ii = (idx / m_strides[0]) + m_dx(j, 0);
954 ptrdiff_t jj = (idx % m_strides[0]) + m_dx(j, 1);
955 if (ii < 0 || ii >= m_shape[0] || jj < 0 || jj >= m_shape[1]) {
956 return -1;
957 }
958 return ii * m_shape[1] + jj;
959 }
960 else {
961 auto index = xt::unravel_from_strides(idx, m_strides, xt::layout_type::row_major);
962 for (size_t d = 0; d < Dim; ++d) {
963 index[d] += m_dx(j, d);
964 if constexpr (!Periodic) {
965 if (index[d] < 0 || index[d] >= m_shape[d]) {
966 return -1;
967 }
968 }
969 else {
970 auto n = m_shape[d];
971 index[d] = (n + (index[d] % n)) % n;
972 }
973 }
974 return xt::ravel_index(index, m_shape, xt::layout_type::row_major);
975 }
976 }
977
978 void label_impl(size_t idx)
979 {
980 size_t nconnected = 0;
981
982 for (size_t j = 0; j < m_dx.shape(0); ++j) {
983 ptrdiff_t compare = (this->*get_compare)(idx, j);
984 if constexpr (!Periodic) {
985 if (compare == -1) {
986 continue;
987 }
988 }
989 if (m_label.flat(compare) != 0) {
990 m_connected[nconnected] = m_renum[m_label.flat(compare)];
991 nconnected++;
992 }
993 }
994
995 if (nconnected == 0) {
996 m_label.flat(idx) = m_new_label;
997 m_new_label += 1;
998 return;
999 }
1000
1001 if (nconnected == 1) {
1002 m_label.flat(idx) = m_connected[0];
1003 return;
1004 }
1005
1006 nconnected = this->unique(&m_connected[0], nconnected);
1007 if (nconnected == 1) {
1008 m_label.flat(idx) = m_connected[0];
1009 return;
1010 }
1011
1012 // mark all labels in the list for merging
1013 // `m_label` is not yet updated to avoid looping over all blocks too frequently
1014 // the new label can be read by `m_renum[lab]` (as done above)
1015 m_label.flat(idx) = this->merge(&m_connected[0], nconnected);
1016 m_nmerge++;
1017 }
1018
1019public:
1024 template <class T>
1025 void add_image(const T& img)
1026 {
1027 GOOSEEYE_ASSERT(xt::has_shape(img, m_label.shape()), std::out_of_range);
1028
1029 for (size_t idx = 0; idx < img.size(); ++idx) {
1030 if (img.flat(idx) == 0) {
1031 continue;
1032 }
1033 if (m_label.flat(idx) != 0) {
1034 continue;
1035 }
1036 this->label_impl(idx);
1037 }
1038 this->apply_merge();
1039 }
1040
1041private:
1042 template <class T>
1043 bool legal_points(const T& begin, const T& end)
1044 {
1045 size_t n = m_label.size();
1046 if constexpr (std::is_signed_v<typename T::value_type>) {
1047 return !std::any_of(begin, end, [n](size_t i) { return i < 0 || i >= n; });
1048 }
1049 else {
1050 return !std::any_of(begin, end, [n](size_t i) { return i >= n; });
1051 }
1052 }
1053
1054public:
1060 template <class T>
1061 void add_points(const T& begin, const T& end)
1062 {
1063 GOOSEEYE_ASSERT(this->legal_points(begin, end), std::out_of_range);
1064 for (auto it = begin; it != end; ++it) {
1065 if (m_label.flat(*it) != 0) {
1066 continue;
1067 }
1068 this->label_impl(*it);
1069 }
1070 this->apply_merge();
1071 }
1072
1077 template <class T>
1078 void add_points(const T& idx)
1079 {
1080 GOOSEEYE_ASSERT(idx.dimension() == 1, std::out_of_range);
1081 return this->add_points(idx.begin(), idx.end());
1082 }
1083
1092 template <class T>
1093 std::vector<size_t> add_sequence(const T& idx)
1094 {
1095 GOOSEEYE_ASSERT(idx.dimension() == 1, std::out_of_range);
1096 GOOSEEYE_ASSERT(idx.size() >= 1, std::out_of_range);
1097 GOOSEEYE_ASSERT(this->legal_points(idx.begin(), idx.end()), std::out_of_range);
1098 std::vector<size_t> ret;
1099 size_t i = 0;
1100 while (true) {
1101 auto nl = m_new_label;
1102 auto nm = m_nmerge;
1103 auto lab = m_label.flat(idx(i));
1104
1105 for (; i < idx.size(); ++i) {
1106 auto l = m_label.flat(idx(i));
1107 if (l != lab && l != 0) {
1108 ret.push_back(i);
1109 break;
1110 }
1111 if (l != 0) {
1112 continue;
1113 }
1114 this->label_impl(idx(i));
1115 if (m_new_label != nl || m_nmerge != nm) {
1116 ret.push_back(i);
1117 break;
1118 }
1119 }
1120
1121 if (i == idx.size()) {
1122 break;
1123 }
1124 }
1125
1126 this->apply_merge();
1127 ret.push_back(idx.size());
1128 return ret;
1129 }
1130
1135 std::string repr() const
1136 {
1137 return detail::get_namespace() + "ClusterLabeller" + std::to_string(Dim) + " " +
1138 detail::shape_to_string(m_shape);
1139 }
1140
1145 const auto& shape() const
1146 {
1147 return m_label.shape();
1148 }
1149
1154 auto size() const
1155 {
1156 return m_label.size();
1157 }
1158
1159 // todo: allow resetting a cluster map ?
1160
1165 const auto& labels() const
1166 {
1167 return m_label;
1168 }
1169};
1170
1171namespace detail {
1172
1173template <size_t Dimension, bool Periodicity>
1174class ClusterLabellerOverload : public ClusterLabeller<Dimension, Periodicity> {
1175public:
1176 template <class T>
1177 ClusterLabellerOverload(const T& img) : ClusterLabeller<Dimension, Periodicity>(img.shape())
1178 {
1179 this->add_image(img);
1180 this->prune();
1181 }
1182
1183 auto get() const
1184 {
1185 return this->labels();
1186 }
1187};
1188
1189} // namespace detail
1190
1197template <class T>
1198inline array_type::array<int> clusters(const T& f, bool periodic = true)
1199{
1200 GOOSEEYE_ASSERT(f.layout() == xt::layout_type::row_major, std::runtime_error);
1201
1202 auto n = f.dimension();
1203 if (n == 1 && periodic) {
1204 return detail::ClusterLabellerOverload<1, true>(f).get();
1205 }
1206 if (n == 1 && !periodic) {
1207 return detail::ClusterLabellerOverload<1, false>(f).get();
1208 }
1209 if (n == 2 && periodic) {
1210 return detail::ClusterLabellerOverload<2, true>(f).get();
1211 }
1212 if (n == 2 && !periodic) {
1213 return detail::ClusterLabellerOverload<2, false>(f).get();
1214 }
1215 if (n == 3 && periodic) {
1216 return detail::ClusterLabellerOverload<3, true>(f).get();
1217 }
1218 if (n == 3 && !periodic) {
1219 return detail::ClusterLabellerOverload<3, false>(f).get();
1220 }
1221 throw std::runtime_error("Please use ClusterLabeller directly for dimensions > 3.");
1222}
1223
1247 const array_type::tensor<double, 1>& shape,
1248 const array_type::tensor<double, 2>& positions,
1249 bool periodic = true)
1250{
1251 if (positions.size() == 0) {
1252 return xt::zeros<double>({shape.size()});
1253 }
1254
1255 if (!periodic) {
1256 return xt::mean(positions, 0);
1257 }
1258 else {
1259 double pi = xt::numeric_constants<double>::PI;
1260 auto theta = 2.0 * pi * positions / shape;
1261 auto xi = xt::cos(theta);
1262 auto zeta = xt::sin(theta);
1263 auto xi_bar = xt::mean(xi, 0);
1264 auto zeta_bar = xt::mean(zeta, 0);
1265 auto theta_bar = xt::atan2(-zeta_bar, -xi_bar) + pi;
1266 return shape * theta_bar / (2.0 * pi);
1267 }
1268}
1269
1275 const array_type::tensor<double, 1>& shape,
1276 const array_type::tensor<double, 2>& positions,
1277 const array_type::tensor<double, 1>& weights,
1278 bool periodic = true)
1279{
1280 if (positions.size() == 0) {
1281 return xt::zeros<double>({shape.size()});
1282 }
1283
1284 if (!periodic) {
1285 return xt::average(positions, weights, 0);
1286 }
1287 else {
1288 double pi = xt::numeric_constants<double>::PI;
1289 auto theta = 2.0 * pi * positions / shape;
1290 auto xi = xt::cos(theta);
1291 auto zeta = xt::sin(theta);
1292 auto xi_bar = xt::average(xi, weights, 0);
1293 auto zeta_bar = xt::average(zeta, weights, 0);
1294 auto theta_bar = xt::atan2(-zeta_bar, -xi_bar) + pi;
1295 return shape * theta_bar / (2.0 * pi);
1296 }
1297}
1298
1299namespace detail {
1300
1301class PositionList {
1302public:
1305
1306 PositionList() = default;
1307
1308 template <class T>
1309 void set(const T& condition)
1310 {
1311 positions = xt::from_indices(xt::argwhere(condition));
1312 }
1313
1314 template <class T, class W>
1315 void set(const T& condition, const W& w)
1316 {
1317 auto pos = xt::argwhere(condition);
1318 weights = xt::empty<double>({pos.size()});
1319 for (size_t i = 0; i < pos.size(); ++i) {
1320 weights(i) = w[pos[i]];
1321 }
1322 positions = xt::from_indices(pos);
1323 }
1324};
1325
1326} // namespace detail
1327
1338template <class T, class N>
1340labels_centers(const T& labels, const N& names, bool periodic = true)
1341{
1342 static_assert(std::is_integral<typename T::value_type>::value, "Integral labels required.");
1343 GOOSEEYE_ASSERT(labels.dimension() > 0, std::out_of_range);
1344 GOOSEEYE_ASSERT(names.dimension() == 1, std::out_of_range);
1345
1346 size_t rank = labels.dimension();
1347 array_type::tensor<double, 1> shape = xt::adapt(labels.shape());
1348 array_type::tensor<double, 2> ret = xt::zeros<double>({names.size(), rank});
1349 detail::PositionList plist;
1350
1351 for (size_t l = 0; l < names.size(); ++l) {
1352 plist.set(xt::equal(labels, names(l)));
1353 if (plist.positions.size() == 0) {
1354 continue;
1355 }
1356 xt::view(ret, l, xt::all()) = center(shape, plist.positions, periodic);
1357 }
1358
1359 return ret;
1360}
1361
1366template <class T, class W, class N>
1368labels_centers_of_mass(const T& labels, const W& weights, const N& names, bool periodic = true)
1369{
1370 static_assert(std::is_integral<typename T::value_type>::value, "Integral labels required.");
1371 GOOSEEYE_ASSERT(xt::has_shape(labels, weights.shape()), std::out_of_range);
1372 GOOSEEYE_ASSERT(labels.dimension() > 0, std::out_of_range);
1373 GOOSEEYE_ASSERT(names.dimension() == 1, std::out_of_range);
1374
1375 size_t rank = labels.dimension();
1376 array_type::tensor<double, 1> shape = xt::adapt(labels.shape());
1377 array_type::tensor<double, 2> ret = xt::zeros<double>({names.size(), rank});
1378 detail::PositionList plist;
1379
1380 for (size_t l = 0; l < names.size(); ++l) {
1381 plist.set(xt::equal(labels, names(l)), weights);
1382 if (plist.positions.size() == 0) {
1383 continue;
1384 }
1385 xt::view(ret, l, xt::all()) =
1386 center_of_mass(shape, plist.positions, plist.weights, periodic);
1387 }
1388
1389 return ret;
1390}
1391
1403public:
1407 Ensemble() = default;
1408
1415 Ensemble(const std::vector<size_t>& roi, bool periodic = true, bool variance = true);
1416
1422
1428
1434
1440
1446
1452
1459 array_type::array<double> distance(size_t axis) const;
1460
1467 array_type::array<double> distance(const std::vector<double>& h) const;
1468
1477 array_type::array<double> distance(const std::vector<double>& h, size_t axis) const;
1478
1483 template <class T>
1484 void mean(const T& f);
1485
1491 template <class T, class M>
1492 void mean(const T& f, const M& fmask);
1493
1499 template <class T>
1500 void S2(const T& f, const T& g);
1501
1509 template <class T, class M>
1510 void S2(const T& f, const T& g, const M& fmask, const M& gmask);
1511
1517 template <class T>
1518 void C2(const T& f, const T& g);
1519
1527 template <class T, class M>
1528 void C2(const T& f, const T& g, const M& fmask, const M& gmask);
1529
1535 template <class T>
1536 void W2(const T& w, const T& f);
1537
1544 template <class T, class M>
1545 void W2(const T& w, const T& f, const M& fmask);
1546
1554 template <class C, class T>
1555 void
1556 W2c(const C& clusters, const C& centers, const T& f, path_mode mode = path_mode::Bresenham);
1557
1566 template <class C, class T, class M>
1567 void
1568 W2c(const C& clusters,
1569 const C& centers,
1570 const T& f,
1571 const M& fmask,
1573
1581 template <class T>
1582 void heightheight(const T& f);
1583
1588 template <class T, class M>
1589 void heightheight(const T& f, const M& fmask);
1590
1596 template <class T>
1597 void L(const T& f, path_mode mode = path_mode::Bresenham);
1598
1599private:
1600 // Type: used to lock the ensemble to a certain measure.
1601 enum class Type { Unset, mean, S2, C2, W2, W2c, L, heightheight };
1602
1603 // Initialize class as unlocked.
1604 Type m_stat = Type::Unset;
1605
1606 // Maximum number of dimensions.
1607 static const size_t MAX_DIM = 3;
1608
1609 // Switch to assume periodicity (for the entire ensemble).
1610 bool m_periodic;
1611
1612 // Switch to compute the variance (for the entire ensemble).
1613 bool m_variance;
1614
1615 // Raw (not normalized) result, and normalization:
1616 // - sum of the first moment: x_1 + x_2 + ...
1618 // - sum of the second moment: x_1^2 + x_2^2 + ...
1620 // - number of measurements per pixel
1622
1623 // Shape of the region-of-interest, as specified.
1624 std::vector<size_t> m_shape_orig;
1625
1626 // 3d equivalent of "m_shape_orig".
1627 std::vector<size_t> m_shape;
1628
1629 // Pad size (3d).
1630 std::vector<std::vector<size_t>> m_pad;
1631};
1632
1633// ---------------------------------------------------------
1634// Wrapper functions to compute the statistics for one image
1635// ---------------------------------------------------------
1636
1642inline auto distance(const std::vector<size_t>& roi);
1643
1650inline auto distance(const std::vector<size_t>& roi, size_t axis);
1651
1658inline auto distance(const std::vector<size_t>& roi, const std::vector<double>& h);
1659
1667inline auto distance(const std::vector<size_t>& roi, const std::vector<double>& h, size_t axis);
1668
1676template <class T>
1677inline auto S2(const std::vector<size_t>& roi, const T& f, const T& g, bool periodic = true);
1678
1688template <class T, class M>
1689inline auto
1690S2(const std::vector<size_t>& roi,
1691 const T& f,
1692 const T& g,
1693 const M& fmask,
1694 const M& gmask,
1695 bool periodic = true);
1696
1704template <class T>
1705inline auto C2(const std::vector<size_t>& roi, const T& f, const T& g, bool periodic = true);
1706
1716template <class T, class M>
1717inline auto
1718C2(const std::vector<size_t>& roi,
1719 const T& f,
1720 const T& g,
1721 const M& fmask,
1722 const M& gmask,
1723 bool periodic = true);
1724
1732template <class T>
1733inline auto W2(const std::vector<size_t>& roi, const T& w, const T& f, bool periodic = true);
1734
1743template <class T, class M>
1744inline auto
1745W2(const std::vector<size_t>& roi, const T& w, const T& f, const M& fmask, bool periodic = true);
1746
1756template <class C, class T>
1757inline auto
1758W2c(const std::vector<size_t>& roi,
1759 const C& clusters,
1760 const C& centers,
1761 const T& f,
1763 bool periodic = true);
1764
1775template <class C, class T, class M>
1776inline auto
1777W2c(const std::vector<size_t>& roi,
1778 const C& clusters,
1779 const C& centers,
1780 const T& f,
1781 const M& fmask,
1783 bool periodic = true);
1784
1791template <class T>
1792inline auto heightheight(const std::vector<size_t>& roi, const T& f, bool periodic = true);
1793
1801template <class T, class M>
1802inline auto
1803heightheight(const std::vector<size_t>& roi, const T& f, const M& fmask, bool periodic = true);
1804
1812template <class T>
1813inline auto
1814L(const std::vector<size_t>& roi,
1815 const T& f,
1816 bool periodic = true,
1818
1819} // namespace GooseEYE
1820
1821#include "Ensemble.hpp"
1822#include "Ensemble_C2.hpp"
1823#include "Ensemble_L.hpp"
1824#include "Ensemble_S2.hpp"
1825#include "Ensemble_W2.hpp"
1826#include "Ensemble_W2c.hpp"
1828#include "Ensemble_mean.hpp"
1829#include "GooseEYE.hpp"
1830
1831#endif
(Incrementally) Label clusters (0 as background, 1..n as labels).
Definition GooseEYE.h:672
void prune()
Prune: renumber labels to lowest possible label, see also AvalancheSegmenter::nlabels.
Definition GooseEYE.h:788
void add_image(const T &img)
Add image.
Definition GooseEYE.h:1025
auto size() const
Size of ClusterLabeller::s and ClusterLabeller::labels (== prod(shape)).
Definition GooseEYE.h:1154
ClusterLabeller(const T &shape)
Definition GooseEYE.h:714
ClusterLabeller(const T &shape, const K &kernel)
Definition GooseEYE.h:738
std::string repr() const
Basic class info.
Definition GooseEYE.h:1135
const auto & shape() const
Shape of ClusterLabeller::s and ClusterLabeller::labels.
Definition GooseEYE.h:1145
static constexpr bool Periodic
Periodicity of the system.
Definition GooseEYE.h:675
void add_points(const T &begin, const T &end)
Add sequence of points.
Definition GooseEYE.h:1061
const auto & labels() const
Per block, the label (0 for background).
Definition GooseEYE.h:1165
std::vector< size_t > add_sequence(const T &idx)
Add a sequence of points.
Definition GooseEYE.h:1093
void add_points(const T &idx)
Add sequence of points.
Definition GooseEYE.h:1078
void reset()
Reset labels to zero.
Definition GooseEYE.h:776
static constexpr size_t Dim
Dimensionality of the system.
Definition GooseEYE.h:674
Compute ensemble averaged statistics, by repetitively calling the member-function of a certain statis...
Definition GooseEYE.h:1402
void mean(const T &f)
Add realization to arithmetic mean.
void L(const T &f, path_mode mode=path_mode::Bresenham)
Add realization to lineal-path function.
void S2(const T &f, const T &g)
Add realization to 2-point correlation: P(f(i) * g(i + di)).
array_type::array< double > data_second() const
Get raw-data: ensemble sum of the second moment: x_1^2 + x_2^2 + ...
Definition Ensemble.hpp:59
Ensemble()=default
Constructor.
array_type::array< double > variance() const
Get ensemble variance.
Definition Ensemble.hpp:37
void heightheight(const T &f)
Add realization to height-height correlation.
void W2c(const C &clusters, const C &centers, const T &f, path_mode mode=path_mode::Bresenham)
Add realization to collapsed weighted 2-point correlation.
array_type::array< double > data_first() const
Get raw-data: ensemble sum of the first moment: x_1 + x_2 + ...
Definition Ensemble.hpp:53
void W2(const T &w, const T &f)
Add realization to weighted 2-point correlation.
array_type::array< double > distance() const
Get the relative distance of each pixel in the 'region-of-interest' to its center.
Definition Ensemble.hpp:103
array_type::array< double > result() const
Get ensemble average.
Definition Ensemble.hpp:26
array_type::array< double > norm() const
Get raw-data: normalisation (number of measurements per pixel).
Definition Ensemble.hpp:65
void C2(const T &f, const T &g)
Add realization to 2-point cluster function: P(f(i) == g(i + di)).
#define GOOSEEYE_ASSERT(expr, assertion)
All assertions are implemented as:
Definition config.h:91
xt::xtensor< T, N > tensor
Fixed (static) rank array.
Definition config.h:155
array_type::array< int > nearest(size_t ndim)
Return kernel with nearest neighbours.
Definition GooseEYE.h:63
Toolbox to compute statistics.
Definition config.h:128
path_mode
Different methods to compute a pixel-path.
Definition GooseEYE.h:93
@ Bresenham
Bresenham algorithm.
@ actual
The actual path.
@ full
Similar to actual selecting every voxel that is crossed.
array_type::tensor< double, 2 > labels_centers_of_mass(const T &labels, const W &weights, const N &names, bool periodic=true)
Get the position of the center of each label.
Definition GooseEYE.h:1368
array_type::tensor< double, 1 > center_of_mass(const array_type::tensor< double, 1 > &shape, const array_type::tensor< double, 2 > &positions, const array_type::tensor< double, 1 > &weights, bool periodic=true)
Return the geometric center of a list of positions.
Definition GooseEYE.h:1274
array_type::tensor< double, 1 > center(const array_type::tensor< double, 1 > &shape, const array_type::tensor< double, 2 > &positions, bool periodic=true)
Return the geometric center of a list of positions.
Definition GooseEYE.h:1246
auto L(const std::vector< size_t > &roi, const T &f, bool periodic=true, path_mode mode=path_mode::Bresenham)
Lineal-path function.
Definition GooseEYE.hpp:159
T dilate(const T &f, const S &kernel, const array_type::tensor< size_t, 1 > &iterations, bool periodic=true)
Dilate image.
Definition GooseEYE.h:299
T labels_prune(const T &labels)
Prune labels: renumber labels to lowest possible label starting from 1.
Definition GooseEYE.h:490
auto W2c(const std::vector< size_t > &roi, const C &clusters, const C &centers, const T &f, path_mode mode=path_mode::Bresenham, bool periodic=true)
Collapsed weighted 2-point correlation.
Definition GooseEYE.hpp:115
array_type::tensor< typename T::value_type, 2 > labels_sizes(const T &labels)
Size per label.
Definition GooseEYE.h:588
array_type::tensor< typename T::value_type, 2 > labels_map(const T &a, const T &b)
Get a map to relabel from a to b.
Definition GooseEYE.h:428
auto C2(const std::vector< size_t > &roi, const T &f, const T &g, bool periodic=true)
2-point cluster function: P(f(i) == g(i + di)).
Definition GooseEYE.hpp:75
array_type::array< int > clusters(const T &f, bool periodic=true)
Compute clusters.
Definition GooseEYE.h:1198
array_type::tensor< double, 2 > labels_centers(const T &labels, const N &names, bool periodic=true)
Get the position of the center of each label.
Definition GooseEYE.h:1340
L labels_rename(const L &labels, const A &rename)
Rename labels.
Definition GooseEYE.h:457
auto W2(const std::vector< size_t > &roi, const T &w, const T &f, bool periodic=true)
Weighted 2-point correlation.
Definition GooseEYE.hpp:97
array_type::array< int > dummy_circles(const std::vector< size_t > &shape, const array_type::tensor< int, 1 > &row, const array_type::tensor< int, 1 > &col, const array_type::tensor< int, 1 > &r, bool periodic=true)
Dummy image with circles.
Definition GooseEYE.h:120
array_type::tensor< int, 2 > path(const array_type::tensor< int, 1 > &x0, const array_type::tensor< int, 1 > &x1, path_mode mode=path_mode::Bresenham)
Compute a path between two pixels.
Definition GooseEYE.hpp:15
auto distance(const std::vector< size_t > &roi)
Get the relative distance of each pixel in the 'region-of-interest' to its center.
Definition GooseEYE.hpp:28
auto S2(const std::vector< size_t > &roi, const T &f, const T &g, bool periodic=true)
2-point correlation: P(f(i) * g(i + di)).
Definition GooseEYE.hpp:53
auto heightheight(const std::vector< size_t > &roi, const T &f, bool periodic=true)
Height-height correlation.
Definition GooseEYE.hpp:143
L labels_reorder(const L &labels, const A &order)
Reorder labels.
Definition GooseEYE.h:536