|
| 1 | +using Base.Cartesian |
| 2 | +using StaticArrays |
| 3 | + |
| 4 | +export Lanczos, Lanczos4OpenCV |
| 5 | + |
| 6 | +abstract type AbstractLanczos <: InterpolationType end |
| 7 | + |
| 8 | +""" |
| 9 | + Lanczos{N}(a=4) |
| 10 | +
|
| 11 | +Lanczos resampling via a kernel with scale parameter `a` and support over `N` neighbors. |
| 12 | +
|
| 13 | +This form of interpolation is merely the discrete convolution of the samples with a Lanczos kernel of size `a`. The size is directly related to how "far" the interpolation will reach for information, and has `O(N^2)` impact on runtime. An alternative implementation matching `lanczos4` from OpenCV is available as Lanczos4OpenCV. |
| 14 | +""" |
| 15 | +struct Lanczos{N} <: AbstractLanczos |
| 16 | + a::Int |
| 17 | + |
| 18 | + function Lanczos{N}(a) where N |
| 19 | + N < a && @warn "Using a smaller support than scale for Lanczos window. Proceed with caution." |
| 20 | + new{N}(a) |
| 21 | + end |
| 22 | +end |
| 23 | + |
| 24 | +Lanczos(a=4) = Lanczos{a}(a) |
| 25 | + |
| 26 | +""" |
| 27 | + LanczosInterpolation |
| 28 | +""" |
| 29 | +struct LanczosInterpolation{T,N,IT <: DimSpec{AbstractLanczos},A <: AbstractArray{T,N},P <: Tuple{Vararg{AbstractArray,N}}} <: AbstractInterpolation{T,N,IT} |
| 30 | + coefs::A |
| 31 | + parentaxes::P |
| 32 | + it::IT |
| 33 | +end |
| 34 | + |
| 35 | +@generated degree(::Lanczos{N}) where {N} = :($N) |
| 36 | + |
| 37 | +getknots(itp::LanczosInterpolation) = axes(itp) |
| 38 | +coefficients(itp::LanczosInterpolation) = itp.coefs |
| 39 | +itpflag(itp::LanczosInterpolation) = itp.it |
| 40 | + |
| 41 | +size(itp::LanczosInterpolation) = map(length, itp.parentaxes) |
| 42 | +axes(itp::LanczosInterpolation) = itp.parentaxes |
| 43 | +lbounds(itp::LanczosInterpolation) = map(first, itp.parentaxes) |
| 44 | +ubounds(itp::LanczosInterpolation) = map(last, itp.parentaxes) |
| 45 | + |
| 46 | +function interpolate(A::AbstractArray{T}, it::AbstractLanczos) where T |
| 47 | + Apad = copy_with_padding(float(T), A, it) |
| 48 | + return LanczosInterpolation(Apad, axes(A), it) |
| 49 | +end |
| 50 | + |
| 51 | +@inline function (itp::LanczosInterpolation{T,N})(x::Vararg{<:Number,N}) where {T,N} |
| 52 | + @boundscheck (checkbounds(Bool, itp, x...) || Base.throw_boundserror(itp, x)) |
| 53 | + wis = weightedindexes((value_weights,), itpinfo(itp)..., x) |
| 54 | + itp.coefs[wis...] |
| 55 | +end |
| 56 | + |
| 57 | +function weightedindex_parts(fs, it::AbstractLanczos, ax::AbstractUnitRange{<:Integer}, x) |
| 58 | + pos, δx = positions(it, ax, x) |
| 59 | + (position = pos, coefs = fmap(fs, it, δx)) |
| 60 | +end |
| 61 | + |
| 62 | +function positions(it::AbstractLanczos, ax, x) |
| 63 | + xf = floorbounds(x, ax) |
| 64 | + δx = x - xf |
| 65 | + fast_trunc(Int, xf) - degree(it) + 1, δx |
| 66 | +end |
| 67 | + |
| 68 | +function value_weights(it::Lanczos, δx::S) where S |
| 69 | + N = degree(it) |
| 70 | + # short-circuit if integral |
| 71 | + isinteger(δx) && return ntuple(i->convert(float(S), i == N - δx), Val(2N)) |
| 72 | + |
| 73 | + # LUTs |
| 74 | + #it.a === N === 4 && return _lanczos4(δx) |
| 75 | + |
| 76 | + cs = ntuple(i -> lanczos(N - i + δx, it.a, N), Val(2N)) |
| 77 | + sum_cs = sum(cs) |
| 78 | + normed_cs = ntuple(i -> cs[i] / sum_cs, Val(length(cs))) |
| 79 | + return normed_cs |
| 80 | +end |
| 81 | + |
| 82 | +function padded_axis(ax::AbstractUnitRange, it::AbstractLanczos) |
| 83 | + N = degree(it) |
| 84 | + return first(ax) - N + 1:last(ax) + N |
| 85 | +end |
| 86 | + |
| 87 | +# precise implementations for fast evaluation of common kernels |
| 88 | + |
| 89 | +""" |
| 90 | + lanczos(x, a, n=a) |
| 91 | +
|
| 92 | +Implementation of the [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling) |
| 93 | +""" |
| 94 | +lanczos(x::T, a::Integer, n=a) where {T} = abs(x) < n ? T(sinc(x) * sinc(x / a)) : zero(T) |
| 95 | + |
| 96 | +""" |
| 97 | + Lanczos4OpenCV() |
| 98 | +
|
| 99 | +Alternative implementation of Lanczos resampling using algorithm `lanczos4` function of OpenCV: |
| 100 | +https://github.com/opencv/opencv/blob/de15636724967faf62c2d1bce26f4335e4b359e5/modules/imgproc/src/resize.cpp#L917-L946 |
| 101 | +""" |
| 102 | +struct Lanczos4OpenCV <: AbstractLanczos end |
| 103 | + |
| 104 | +degree(::Lanczos4OpenCV) = 4 |
| 105 | + |
| 106 | +value_weights(::Lanczos4OpenCV, δx::S) where S = ifelse(isinteger(δx),ntuple(i->convert(float(S), i == 4 - δx), Val(8)) ,_lanczos4_opencv(δx)) |
| 107 | + |
| 108 | +# s45 = sqrt(2)/2 |
| 109 | +const s45 = 0.70710678118654752440084436210485 |
| 110 | +""" |
| 111 | + l4_2d_cs is a lookup table that could be generated by |
| 112 | + x = (0:7)*45*5 |
| 113 | + l4_2d_cs = [cosd.(x) sind.(x)]' |
| 114 | +""" |
| 115 | +const l4_2d_cs = SA[1 0; -s45 -s45; 0 1; s45 -s45; -1 0; s45 s45; 0 -1; -s45 s45] |
| 116 | + |
| 117 | + |
| 118 | +function _lanczos4_opencv(δx) |
| 119 | + p_4 = π / 4 |
| 120 | + y0 = -(δx + 3) * p_4 |
| 121 | + s0, c0 = sincos(y0) |
| 122 | + cs = ntuple(8) do i |
| 123 | + y = (δx + 4 - i) * p_4 |
| 124 | + # Numerator is the sin subtraction identity |
| 125 | + # It is equivalent to the following |
| 126 | + # f(δx,i) = sin( π/4*( 5*(i-1)-δx-3 ) ) |
| 127 | + (l4_2d_cs[i, 1] * s0 + l4_2d_cs[i, 2] * c0) / y^2 |
| 128 | + end |
| 129 | + sum_cs = sum(cs) |
| 130 | + normed_cs = ntuple(i -> cs[i] / sum_cs, Val(8)) |
| 131 | + return normed_cs |
| 132 | +end |
0 commit comments