1 /** 2 Value interpolation module. 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.imgproc.interpolate; 11 12 import std.traits : isNumeric, isScalarType, isIntegral, allSameType, ReturnType, 13 isFloatingPoint; 14 import std.meta : allSatisfy; 15 16 import mir.math.common : fastmath; 17 18 import mir.ndslice.slice; 19 20 21 /** 22 Test if given function is proper form for interpolation. 23 */ 24 static bool isInterpolationFunc(alias F)() 25 { 26 auto s = [0., 1.].sliced(2); 27 return (__traits(compiles, F(s, 0))); // TODO: check the return type? 28 } 29 30 /** 31 Test for 1D (vector) interpolation function. 32 */ 33 static bool isInterpolationFunc1D(alias F)() 34 { 35 return isInterpolationFunc!F; 36 } 37 38 /** 39 Test for 2D (matrix) interpolation function. 40 */ 41 static bool isInterpolationFunc2D(alias F)() 42 { 43 auto s = [0, 1, 2, 3].sliced(2, 2); 44 return (__traits(compiles, F(s, 3, 3))); 45 } 46 47 unittest 48 { 49 static assert(isInterpolationFunc!linear); 50 static assert(isInterpolationFunc1D!linear); 51 static assert(isInterpolationFunc2D!linear); 52 } 53 54 /** 55 Linear interpolation. 56 57 Params: 58 slice = Input slice which values are interpolated. 59 pos = Position on which slice values are interpolated. 60 61 Returns: 62 Interpolated resulting value. 63 */ 64 pure T linear(SliceKind kind, size_t[] packs, T, P, size_t N)(Slice!(kind, packs, T*) slice, P[N] pos...) 65 if (packs.length == 1) 66 { 67 // TODO: document 68 static assert(N == packs[0], "Interpolation indexing has to be of same dimension as the input slice."); 69 70 static if (pos.length == 1) 71 { 72 return linearImpl_1(slice, pos[0]); 73 } 74 else static if (pos.length == 2) 75 { 76 return linearImpl_2(slice, pos[0], pos[1]); 77 } 78 else 79 { 80 static assert(0, "Unsupported slice dimension for linear interpolation."); 81 } 82 } 83 84 unittest 85 { 86 auto arr1 = [0., 1.].sliced(2); 87 assert(linear(arr1, 0.) == 0.); 88 assert(linear(arr1, 1.) == 1.); 89 assert(linear(arr1, 0.1) == 0.1); 90 assert(linear(arr1, 0.5) == 0.5); 91 assert(linear(arr1, 0.9) == 0.9); 92 93 auto arr1_integral = [0, 10].sliced(2); 94 assert(linear(arr1_integral, 0.) == 0); 95 assert(linear(arr1_integral, 1.) == 10); 96 assert(linear(arr1_integral, 0.1) == 1); 97 assert(linear(arr1_integral, 0.5) == 5); 98 assert(linear(arr1_integral, 0.9) == 9); 99 100 auto arr2 = [0., 0., 0., 1.].sliced(2, 2); 101 assert(arr2.linear(0.5, 0.5) == 0.25); 102 assert(arr2.linear(0., 0.) == 0.); 103 assert(arr2.linear(1., 1.) == 1.); 104 assert(arr2.linear(1., 0.) == 0.); 105 } 106 107 private: 108 109 pure @fastmath auto linearImpl_1(T)(Slice!(SliceKind.contiguous, [1], T*) range, double pos) 110 { 111 import mir.math.common; 112 113 assert(pos < range.length); 114 115 if (pos == range.length - 1) 116 { 117 return range[$ - 1]; 118 } 119 120 size_t round = cast(size_t)pos.floor; 121 double weight = pos - cast(double)round; 122 123 static if (isIntegral!T) 124 { 125 // TODO: is this branch really necessary? 126 auto v1 = cast(double)range[round]; 127 auto v2 = cast(double)range[round + 1]; 128 } 129 else 130 { 131 auto v1 = range[round]; 132 auto v2 = range[round + 1]; 133 } 134 return cast(T)(v1 * (1. - weight) + v2 * (weight)); 135 } 136 137 pure @fastmath auto linearImpl_2(SliceKind kind, size_t[] packs, T)(Slice!(kind, packs, T*) range, double pos_x, double pos_y) 138 { 139 import mir.math.common : floor; 140 141 assert(pos_x < range.length!0 && pos_y < range.length!1); 142 143 size_t rx = cast(size_t)pos_x.floor; 144 size_t ry = cast(size_t)pos_y.floor; 145 double wx = pos_x - cast(double)rx; 146 double wy = pos_y - cast(double)ry; 147 148 auto w00 = (1. - wx) * (1. - wy); 149 auto w01 = (wx) * (1. - wy); 150 auto w10 = (1. - wx) * (wy); 151 auto w11 = (wx) * (wy); 152 153 auto x_end = rx == range.length!0 - 1; 154 auto y_end = ry == range.length!1 - 1; 155 156 static if (isIntegral!T) 157 { 158 // TODO: (same as in 1D vesion) is this branch really necessary? 159 double v1, v2, v3, v4; 160 v1 = cast(double)range[rx, ry]; 161 v2 = cast(double)range[x_end ? rx : rx + 1, ry]; 162 v3 = cast(double)range[rx, y_end ? ry : ry + 1]; 163 v4 = cast(double)range[x_end ? rx : rx + 1, y_end ? ry : ry + 1]; 164 } 165 else 166 { 167 T v1, v2, v3, v4; 168 v1 = range[rx, ry]; 169 v2 = range[x_end ? rx : rx + 1, ry]; 170 v3 = range[rx, y_end ? ry : ry + 1]; 171 v4 = range[x_end ? rx : rx + 1, y_end ? ry : ry + 1]; 172 } 173 return cast(T)(v1 * w00 + v2 * w01 + v3 * w10 + v4 * w11); 174 } 175