1 /** 2 Module for various utilities used throughout the library. 3 4 Copyright: Copyright Relja Ljubobratovic 2016. 5 6 Authors: Relja Ljubobratovic 7 8 License: $(LINK3 http://www.boost.org/LICENSE_1_0.txt, Boost Software License - Version 1.0). 9 */ 10 module dcv.core.utils; 11 12 import std.traits; 13 import std.meta : allSatisfy; 14 15 import mir.ndslice.slice; 16 import mir.ndslice.topology: iota, flattened; 17 18 19 /// Convenience method to return an empty slice - used mainly as default argument in functions in library. 20 static Slice!(SliceKind.contiguous, packs, V*) emptySlice(size_t[] packs, V)() pure @safe nothrow 21 { 22 return Slice!(SliceKind.contiguous, packs, V*)(); 23 } 24 25 package(dcv) @nogc pure nothrow 26 { 27 /** 28 Pack and unpack (N, T*) slices to (N-1, T[M]*). 29 */ 30 auto staticPack(size_t CH, SliceKind kind, size_t[] packs, T)(Slice!(kind, packs, T*) slice) 31 if (packs.length == 1) 32 { 33 enum N = packs[0]; 34 size_t[N-1] shape = slice.shape[0 .. N-1]; 35 ptrdiff_t[N-1] strides = [slice.strides[0] / CH, slice.strides[1] / CH]; 36 T[CH]* ptr = cast(T[CH]*)slice._iterator; 37 alias Ret = Slice!(kind, [N-1], T[CH]*); 38 return Ret(shape, strides[0 .. Ret.init._strides.length], ptr); 39 } 40 41 /// ditto 42 auto staticUnpack(size_t CH, SliceKind kind, size_t[] packs, T)(Slice!(kind, packs, T[CH]*) slice) 43 if (packs.length == 1) 44 { 45 enum N = packs[0]; 46 size_t[N+1] shape = [slice.shape[0], slice.shape[1], CH]; 47 ptrdiff_t[N+1] strides = [slice.strides[0] * CH, slice.strides[1] * CH, 1]; 48 T* ptr = cast(T*)slice._iterator; 49 alias Ret = Slice!(kind, [N+1], T*); 50 return Ret(shape, strides[0 .. Ret.init._strides.length], ptr); 51 } 52 53 @safe @nogc nothrow pure auto borders(Shape)(Shape shape, size_t ks) 54 in 55 { 56 static assert(Shape.length == 2, "Non-matrix border extraction is not currently supported."); 57 } 58 body 59 { 60 import std.algorithm.comparison : max; 61 62 static struct Range 63 { 64 import mir.ndslice.iterator; 65 Slice!(SliceKind.contiguous, [1], IotaIterator!ptrdiff_t) rows; 66 Slice!(SliceKind.contiguous, [1], IotaIterator!ptrdiff_t) cols; 67 } 68 69 size_t kh = max(size_t(1), ks / 2); 70 71 Range[4] borders = [ 72 Range(iota(shape[0]), iota(kh)), 73 Range(iota(shape[0]), iota([kh], shape[1] - kh)), 74 Range(iota(kh), iota(shape[1])), 75 Range(iota([kh], shape[0] - kh), iota(shape[1])), 76 ]; 77 78 return borders; 79 } 80 } 81 82 /** 83 Clip value by it's value range. 84 85 Params: 86 v = input value, of the input type 87 88 Returns: 89 Clipped value of the output type. 90 */ 91 static pure nothrow @safe T clip(T, V)(V v) if (isNumeric!V && isNumeric!T) 92 { 93 import std.traits : isFloatingPoint; 94 95 static if (is(T == V)) 96 { 97 return v; 98 } 99 else 100 { 101 if (v <= T.min) 102 return T.min; 103 else if (v >= T.max) 104 return T.max; 105 return cast(T)v; 106 } 107 } 108 109 pure nothrow @safe unittest 110 { 111 int value = 30; 112 assert(clip!int(value) == cast(int)30); 113 } 114 115 pure nothrow @safe unittest 116 { 117 int max_ubyte_value = cast(int)ubyte.max + 1; 118 int min_ubyte_value = cast(int)ubyte.min - 1; 119 assert(clip!ubyte(max_ubyte_value) == cast(ubyte)255); 120 assert(clip!ubyte(min_ubyte_value) == cast(ubyte)0); 121 } 122 123 import std.compiler; 124 125 static if (__VERSION__ >= 2071) 126 { // due to undefined bug in flattened in dmd 2.070.0 127 128 /** 129 * Merge multiple slices into one. 130 * 131 * By input of multiple Slice!(kind, [N], T*) objects, produces one Slice!(kind, [N+1], T*) 132 * object, where length of last dimension is number of input slices. Values 133 * of input slices' elements are copied to resulting slice, where [..., i] element 134 * of j-th input slice is copied to [..., i, j] element of output slice. 135 * 136 * e.g. If three single channel images (Slice!(kind, [2], Iterator)) are merged, output will be 137 * a three channel image (Slice!(kind, [3], Iterator)). 138 * 139 * Params: 140 * slices = Input slices. All must by Slice object with same input template parameters. 141 * 142 * Returns: 143 * For input of n Slice!(Contiguous, [N], T*) objects, outputs Slice!(kind, [N+1], T*) object, where 144 * last dimension size is n. 145 */ 146 pure auto merge(Slices...)(Slices slices) 147 if (Slices.length > 0 && isSlice!(Slices[0]) && allSameType!Slices) 148 { 149 import std.algorithm.iteration : map; 150 import std.array : uninitializedArray; 151 152 alias ElementRange = typeof(slices[0].flattened); 153 alias T = typeof(slices[0].flattened.front); 154 155 immutable D = slices[0].shape.length; 156 const auto length = slices[0].elementsCount; 157 158 auto data = uninitializedArray!(T[])(length * slices.length); 159 ElementRange[slices.length] elRange; 160 161 foreach (i, v; slices) 162 { 163 elRange[i] = v.flattened; 164 } 165 166 auto i = 0; 167 foreach (e; 0 .. length) 168 { 169 foreach (ecol; elRange) 170 { 171 data[i++] = ecol[e]; 172 } 173 } 174 175 size_t[D + 1] newShape; 176 newShape[0 .. D] = slices[0].shape[0 .. D]; 177 newShape[D] = slices.length; 178 179 return data.sliced(newShape); 180 } 181 182 unittest 183 { 184 auto s1 = [1, 2, 3].sliced(3); 185 auto s2 = [4, 5, 6].sliced(3); 186 auto m = merge(s1, s2); 187 assert(m == [1, 4, 2, 5, 3, 6].sliced(3, 2)); 188 } 189 190 unittest 191 { 192 auto s1 = [1, 2, 3, 4].sliced(2, 2); 193 auto s2 = [5, 6, 7, 8].sliced(2, 2); 194 auto m = merge(s1, s2); 195 assert(m == [1, 5, 2, 6, 3, 7, 4, 8].sliced(2, 2, 2)); 196 } 197 } 198 199 /// Check if given function can perform boundary condition test. 200 template isBoundaryCondition(alias bc) 201 { 202 enum bool isBoundaryCondition = true; 203 } 204 205 nothrow @nogc pure 206 { 207 208 /// $(LINK2 https://en.wikipedia.org/wiki/Neumann_boundary_condition, Neumann) boundary condition test 209 auto neumann(SliceKind kind, size_t[] packs, Iterator, size_t N)(Slice!(kind, packs, Iterator) tensor, size_t[N] indices...) 210 { 211 static assert(packs[0] == N, "Invalid index dimension"); 212 return tensor.bcImpl!_neumann(indices); 213 } 214 215 /// $(LINK2 https://en.wikipedia.org/wiki/Periodic_boundary_conditions,Periodic) boundary condition test 216 auto periodic(SliceKind kind, size_t[] packs, Iterator, size_t N)(Slice!(kind, packs, Iterator) tensor, size_t[N] indices...) 217 { 218 static assert(packs[0] == N, "Invalid index dimension"); 219 return tensor.bcImpl!_periodic(indices); 220 } 221 222 /// Symmetric boundary condition test 223 auto symmetric(SliceKind kind, size_t[] packs, Iterator, size_t N)(Slice!(kind, packs, Iterator) tensor, size_t[N] indices...) 224 { 225 static assert(packs[0] == N, "Invalid index dimension"); 226 return tensor.bcImpl!_symmetric(indices); 227 } 228 229 } 230 231 /// Alias for generalized boundary condition test function. 232 template BoundaryConditionTest(SliceKind kind, size_t[] packs, T, size_t N) 233 { 234 alias BoundaryConditionTest = ref T function(ref Slice!(kind, packs, T*) slice, size_t[N] indices...); 235 } 236 237 unittest 238 { 239 import mir.ndslice.allocation; 240 241 static assert(isBoundaryCondition!neumann); 242 static assert(isBoundaryCondition!periodic); 243 static assert(isBoundaryCondition!symmetric); 244 245 /* 246 * [0, 1, 2, 247 * 3, 4, 5, 248 * 6, 7, 8] 249 */ 250 auto s = iota(3, 3).slice; 251 252 assert(s.neumann(-1, -1) == s[0, 0]); 253 assert(s.neumann(0, -1) == s[0, 0]); 254 assert(s.neumann(-1, 0) == s[0, 0]); 255 assert(s.neumann(0, 0) == s[0, 0]); 256 assert(s.neumann(2, 2) == s[2, 2]); 257 assert(s.neumann(3, 3) == s[2, 2]); 258 assert(s.neumann(1, 3) == s[1, 2]); 259 assert(s.neumann(3, 1) == s[2, 1]); 260 261 assert(s.symmetric(-1, -1) == s[1, 1]); 262 assert(s.symmetric(-2, -2) == s[2, 2]); 263 assert(s.symmetric(0, 0) == s[0, 0]); 264 assert(s.symmetric(2, 2) == s[2, 2]); 265 assert(s.symmetric(3, 3) == s[1, 1]); 266 } 267 268 private: 269 270 nothrow @nogc pure: 271 272 auto bcImpl(alias bcfunc, SliceKind kind, size_t[] packs, Iterator, size_t N)(Slice!(kind, packs, Iterator) tensor, size_t[N] indices...) 273 { 274 static if (N == 1) 275 { 276 return tensor[bcfunc(cast(int)indices[0], cast(int)tensor.length)]; 277 } 278 else static if (N == 2) 279 { 280 return tensor[bcfunc(cast(int)indices[0], cast(int)tensor.length!0), 281 bcfunc(cast(int)indices[1], cast(int)tensor.length!1)]; 282 } 283 else static if (N == 3) 284 { 285 return tensor[bcfunc(cast(int)indices[0], cast(int)tensor.length!0), 286 bcfunc(cast(int)indices[1], cast(int)tensor.length!1), bcfunc(cast(int)indices[2], 287 cast(int)tensor.length!2)]; 288 } 289 else 290 { 291 foreach (i, ref id; indices) 292 { 293 id = bcfunc(cast(int)id, cast(int)tensor._lengths[i]); 294 } 295 return tensor[indices]; 296 } 297 } 298 299 int _neumann(int x, int nx) 300 { 301 if (x < 0) 302 { 303 x = 0; 304 } 305 else if (x >= nx) 306 { 307 x = nx - 1; 308 } 309 310 return x; 311 } 312 313 int _periodic(int x, int nx) 314 { 315 if (x < 0) 316 { 317 const int n = 1 - cast(int)(x / (nx + 1)); 318 const int ixx = x + n * nx; 319 320 x = ixx % nx; 321 } 322 else if (x >= nx) 323 { 324 x = x % nx; 325 } 326 327 return x; 328 } 329 330 int _symmetric(int x, int nx) 331 { 332 if (x < 0) 333 { 334 const int borde = nx - 1; 335 const int xx = -x; 336 const int n = cast(int)(xx / borde) % 2; 337 338 if (n) 339 x = borde - (xx % borde); 340 else 341 x = xx % borde; 342 } 343 else if (x >= nx) 344 { 345 const int borde = nx - 1; 346 const int n = cast(int)(x / borde) % 2; 347 348 if (n) 349 x = borde - (x % borde); 350 else 351 x = x % borde; 352 } 353 return x; 354 }