GooseEYE 0.9.1
Loading...
Searching...
No Matches
detail.hpp
Go to the documentation of this file.
1
7#ifndef GOOSEEYE_DETAIL_HPP
8#define GOOSEEYE_DETAIL_HPP
9
10#include "GooseEYE.h"
11
12namespace GooseEYE {
13namespace detail {
14
15/*
16Get the axis after converting an array to 3d.
17See: https://xtensor.readthedocs.io/en/latest/api/xmanipulation.html?highlight=atleast_Nd
18
19@arg rank : Rank of the input array.
20@arg axis : Axis along the input array.
21@ret Axis along the 3d-equivalent-array.
22*/
23inline size_t atleast_3d_axis(size_t rank, size_t axis)
24{
25 size_t N = 3;
26 assert(axis < rank);
27 assert(rank <= N);
28 size_t end = static_cast<size_t>(std::round(double(N - rank) / double(N)));
29 return axis + end;
30}
31
32/*
33Get the axes after converting an array to 3d.
34See: https://xtensor.readthedocs.io/en/latest/api/xmanipulation.html?highlight=atleast_Nd
35
36@arg rank : Rank of the input array.
37@arg axes : Axes along the input array.
38@ret Axes along the 3d-equivalent-array.
39*/
41atleast_3d_axes(size_t rank, const array_type::tensor<size_t, 1>& axes)
42{
43 array_type::tensor<size_t, 1> ret = xt::empty_like(axes);
44 for (size_t i = 0; i < axes.size(); ++i) {
45 ret(i) = atleast_3d_axis(rank, axes(i));
46 }
47 return ret;
48}
49
50/*
51Get the axes after converting an array to 3d.
52See: https://xtensor.readthedocs.io/en/latest/api/xmanipulation.html?highlight=atleast_Nd
53
54@arg rank : Rank of the input array.
55@ret Axes along the 3d-equivalent-array.
56*/
57inline array_type::tensor<size_t, 1> atleast_3d_axes(size_t rank)
58{
59 return atleast_3d_axes(rank, xt::arange<size_t>(rank));
60}
61
62/*
63Return shape as vector.
64
65@arg mat : An n-d array.
66@ret The shape of `arg` as `std::vector<size_t>`.
67*/
68template <class T>
69inline std::vector<size_t> shape(T&& mat)
70{
71 return std::vector<size_t>(mat.shape().cbegin(), mat.shape().cend());
72}
73
74/*
75Compute "shape / 2".
76
77@arg shape : A vector.
78@ret The floored-result.
79*/
80inline std::vector<size_t> half_shape(const std::vector<size_t>& shape)
81{
82 std::vector<size_t> ret = shape;
83
84 for (size_t i = 0; i < ret.size(); ++i) {
85 ret[i] = (shape[i] - shape[i] % 2) / 2;
86 }
87
88 return ret;
89}
90
91/*
92Compute padding-width for a certain kernel.
93This is the number of voxels by which a kernel will overlap along each axis when it is
94convoluted over an image.
95The output is {{begin_1, end_1}, {begin_2, end_2}, ... {begin_N, end_N}}
96
97@arg shape : Shape of the kernel.
98@ret Pad-width.
99*/
100inline std::vector<std::vector<size_t>> pad_width(const std::vector<size_t>& shape)
101{
102 std::vector<std::vector<size_t>> pad(shape.size(), std::vector<size_t>(2));
103
104 for (size_t i = 0; i < shape.size(); ++i) {
105 if (shape[i] % 2 == 0) {
106 pad[i][0] = shape[i] / 2 - 1;
107 pad[i][1] = shape[i] / 2;
108 }
109 else {
110 pad[i][0] = (shape[i] - 1) / 2;
111 pad[i][1] = (shape[i] - 1) / 2;
112 }
113 }
114
115 return pad;
116}
117
118/*
119Overload to compute directly on the object itself, not on its shape.
120*/
121template <class T>
122inline std::vector<std::vector<size_t>> pad_width(const T& a)
123{
124 std::vector<size_t> shape(a.shape().cbegin(), a.shape().cend());
125 return pad_width(shape);
126}
127
128/*
129Compute pixel-path using the Bresenham-algorithm.
130See: https://www.geeksforgeeks.org/bresenhams-algorithm-for-3-d-line-drawing/
131
132@arg xa : Pixel coordinate (e.g. {0, 0, 0}).
133@arg xb : Pixel coordinate (e.g. {10, 5, 0}).
134@ret The path: the coordinate of one pixel per row.
135*/
136namespace path {
137
138template <class T>
139inline array_type::tensor<int, 2> bresenham(const T& xa, const T& xb)
140{
141 int ndim = static_cast<int>(xa.size());
142 std::vector<int> ret;
143
144 // see http://www.luberth.com/plotter/line3d.c.txt.html
145 int a[3], s[3], x[3], d[3], in[2], j, i, iin, nnz = 0;
146
147 // set defaults
148 for (i = 0; i < 3; i++) {
149 a[i] = 1;
150 s[i] = 0;
151 x[i] = 0;
152 d[i] = 0;
153 }
154
155 // calculate:
156 // absolute distance, set to "1" if the distance is zero
157 // sign of the distance (can be -1/+1 or 0)
158 // current position (temporary value)
159 for (i = 0; i < ndim; i++) {
160 a[i] = std::abs((int)xb[i] - (int)xa[i]) << 1;
161 s[i] = SIGN((int)xb[i] - (int)xa[i]);
162 x[i] = (int)xa[i];
163 }
164
165 // determine which direction is dominant
166 for (j = 0; j < 3; j++) {
167 // set the remaining directions
168 iin = 0;
169 for (i = 0; i < 3; i++) {
170 if (i != j) {
171 in[iin] = i;
172 iin += 1;
173 }
174 }
175 // determine if the current direction is dominant
176 if (a[j] >= std::max(a[in[0]], a[in[1]]))
177 break;
178 }
179
180 // set increment in non-dominant directions
181 for (i = 0; i < 2; i++)
182 d[in[i]] = a[in[i]] - (a[j] >> 1);
183 // loop until "x" coincides with "xb"
184 while (true) {
185 // add current voxel to path
186 for (i = 0; i < ndim; i++)
187 ret.push_back(x[i]);
188 // update voxel counter
189 nnz += 1;
190 // check convergence
191 if (x[j] == xb[j])
192 return xt::adapt(ret, {static_cast<size_t>(nnz), static_cast<size_t>(ndim)});
193 // check increment in other directions
194 for (i = 0; i < 2; i++) {
195 if (d[in[i]] >= 0) {
196 x[in[i]] += s[in[i]];
197 d[in[i]] -= a[j];
198 }
199 }
200 // increment
201 x[j] += s[j];
202 for (i = 0; i < 2; i++)
203 d[in[i]] += a[in[i]];
204 }
205
206 return xt::adapt(ret, {static_cast<size_t>(nnz), static_cast<size_t>(ndim)});
207}
208} // namespace path
209
210/*
211Compute pixel-path.
212
213@arg xa : Pixel coordinate (e.g. {0, 0, 0}).
214@arg xb : Pixel coordinate (e.g. {10, 5, 0}).
215@ret The path: the coordinate of one pixel per row.
216*/
217namespace path {
218
219template <class T>
220inline array_type::tensor<int, 2> actual(const T& xa, const T& xb)
221{
222 int ndim = static_cast<int>(xa.size());
223 std::vector<int> ret;
224
225 // position, slope, (length to) next intersection
226 double x[3], v[3], t[3], next[3], sign[3];
227 int isign[3];
228 // active dimensions (for in-plane paths dimension have to be skipped
229 // to avoid dividing by zero)
230 int in[3], iin, nin;
231 // path of the current voxel
232 int cindex[3];
233 int nnz = 1;
234 // counters
235 int i, imin, n;
236
237 // set the direction coefficient in all dimensions; if it is zero this
238 // dimension is excluded from further analysis (i.e. in-plane growth)
239 nin = 0;
240 for (i = 0; i < ndim; i++) {
241 // set origin, store to output array; initiate the position
242 cindex[i] = xa[i];
243 ret.push_back(cindex[i]);
244 // initiate position, set slope
245 x[i] = (double)(xa[i]);
246 v[i] = (double)(xb[i] - xa[i]);
247 // non-zero slope: calculate the sign and the next intersection
248 // with a voxel's edges, and update the list to include this dimension
249 // in the further analysis
250 if (v[i]) {
251 sign[i] = v[i] / fabs(v[i]);
252 isign[i] = (int)sign[i];
253 next[i] = sign[i] * 0.5;
254 in[nin] = i;
255 nin++;
256 }
257 }
258
259 // starting from "xa" loop to "xb"
260 while (true) {
261 // find translation coefficient "t" for each next intersection
262 // (only include dimensions with non-zero slope)
263 for (iin = 0; iin < nin; iin++) {
264 i = in[iin];
265 t[iin] = (next[i] - x[i]) / v[i];
266 }
267 // find the minimum "t": the intersection which is closet along the line
268 // from the current position -> proceed in this direction
269 imin = 0;
270 for (iin = 1; iin < nin; iin++) {
271 if (t[iin] < t[imin]) {
272 imin = iin;
273 }
274 }
275
276 // update path: proceed in dimension of minimum "t"
277 // note: if dimensions have equal "t" -> proceed in each dimension
278 for (iin = 0; iin < nin; iin++) {
279 if (fabs(t[iin] - t[imin]) < 1.e-6) {
280 cindex[in[iin]] += isign[in[iin]];
281 next[in[iin]] += sign[in[iin]];
282 }
283 }
284 // store only the next voxel
285 for (i = 0; i < ndim; i++) {
286 ret.push_back(cindex[i]);
287 }
288 nnz++;
289 // update position, and current path
290 for (i = 0; i < ndim; i++) {
291 x[i] = xa[i] + v[i] * t[imin];
292 }
293
294 // check convergence: stop when "xb" is reached
295 n = 0;
296 for (i = 0; i < ndim; i++) {
297 if (cindex[i] == xb[i]) {
298 n++;
299 }
300 }
301 if (n == ndim) {
302 break;
303 }
304 }
305
306 return xt::adapt(ret, {static_cast<size_t>(nnz), static_cast<size_t>(ndim)});
307}
308} // namespace path
309
310/*
311Compute pixel-path.
312
313@arg xa : Pixel coordinate (e.g. {0, 0, 0}).
314@arg xb : Pixel coordinate (e.g. {10, 5, 0}).
315@ret The path: the coordinate of one pixel per row.
316*/
317namespace path {
318
319template <class T>
320inline array_type::tensor<int, 2> full(const T& xa, const T& xb)
321{
322 int ndim = static_cast<int>(xa.size());
323 std::vector<int> ret;
324
325 // position, slope, (length to) next intersection
326 double x[3], v[3], t[3], next[3], sign[3];
327 int isign[3];
328 // active dimensions (for in-plane paths dimension have to be skipped
329 // to avoid dividing by zero)
330 int in[3], iin, nin;
331 // path of the current voxel
332 int cindex[3];
333 int nnz = 1;
334 // counters
335 int i, imin, n;
336
337 // set the direction coefficient in all dimensions; if it is zero this
338 // dimension is excluded from further analysis (i.e. in-plane growth)
339 nin = 0;
340 for (i = 0; i < ndim; i++) {
341 // set origin, store to output array; initiate the position
342 cindex[i] = xa[i];
343 ret.push_back(cindex[i]);
344 // initiate position, set slope
345 x[i] = (double)(xa[i]);
346 v[i] = (double)(xb[i] - xa[i]);
347 // non-zero slope: calculate the sign and the next intersection
348 // with a voxel's edges, and update the list to include this dimension
349 // in the further analysis
350 if (v[i]) {
351 sign[i] = v[i] / fabs(v[i]);
352 isign[i] = (int)sign[i];
353 next[i] = sign[i] * 0.5;
354 in[nin] = i;
355 nin++;
356 }
357 }
358
359 // starting from "xa" loop to "xb"
360 while (true) {
361 // find translation coefficient "t" for each next intersection
362 // (only include dimensions with non-zero slope)
363 for (iin = 0; iin < nin; iin++) {
364 i = in[iin];
365 t[iin] = (next[i] - x[i]) / v[i];
366 }
367 // find the minimum "t": the intersection which is closet along the line
368 // from the current position -> proceed in this direction
369 imin = 0;
370 for (iin = 1; iin < nin; iin++) {
371 if (t[iin] < t[imin]) {
372 imin = iin;
373 }
374 }
375
376 // update path: proceed in dimension of minimum "t"
377 // note: if dimensions have equal "t" -> proceed in each dimension
378 for (iin = 0; iin < nin; iin++) {
379 if (fabs(t[iin] - t[imin]) < 1.e-6) {
380 cindex[in[iin]] += isign[in[iin]];
381 next[in[iin]] += sign[in[iin]];
382 // store all the face crossings ("mode")
383 for (i = 0; i < ndim; i++) {
384 ret.push_back(cindex[i]);
385 }
386 nnz++;
387 }
388 }
389 // update position, and current path
390 for (i = 0; i < ndim; i++) {
391 x[i] = xa[i] + v[i] * t[imin];
392 }
393
394 // check convergence: stop when "xb" is reached
395 n = 0;
396 for (i = 0; i < ndim; i++) {
397 if (cindex[i] == xb[i]) {
398 n++;
399 }
400 }
401 if (n == ndim) {
402 break;
403 }
404 }
405
406 return xt::adapt(ret, {static_cast<size_t>(nnz), static_cast<size_t>(ndim)});
407}
408} // namespace path
409
410} // namespace detail
411} // namespace GooseEYE
412
413#endif
xt::xtensor< T, N > tensor
Fixed (static) rank array.
Definition config.h:155
Toolbox to compute statistics.
Definition config.h:128
@ actual
The actual path.
@ full
Similar to actual selecting every voxel that is crossed.
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