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