Simple interpolation?

I could do with some maths help!

I have a few equally-spaced values between 0.0 and 1.0:

[0.0, 0.1, 0.85, 0.9, 1.0]

and I want a function f(n) that returns a suitably close value for any other n between 0.0 and 1.0. So a value of 0.5 would lie on some kind of line that meanders through all the known points.

I can draw it in 2D:

but I can’t work out how to code it…

1 Like

Something like this?

julia> using Interpolations, Plots

julia> y = [0.0, 0.1, 0.85, 0.9, 1.0];

julia> x = range(0, stop = 1, length = 5);

julia> itp = interpolate((x,), y, Gridded(Linear()))
5-element interpolate((0.0:0.25:1.0,), ::Vector{Float64}, Gridded(Linear())) with element type Float64:
 0.0
 0.1
 0.85
 0.9
 1.0

julia> itp[0.3]
0.24999999999999997

julia> plot(0:0.01:1.0, [itp[i] for i ∈ 0:0.01:1.0], label = "Interpolation");

julia> scatter!(x, y, label = "Data", legend = :right)

image

(I feel a bit bad responding to a visual wizard like you with such a pedestrian plot…)

12 Likes

Haha, no, that’s awesome - firmly grounded in reality, not “pedestrian” at all.

Code is great - thanks! You make it look easy! :joy:

1 Like

Pleasure to be able to help the creator of JuliaMono, which I use heavily wherever I can - in the grand scheme of things that was probably the greater contribution :slight_smile:

5 Likes

Thanks!

(There’s a lot of interpolation going on in the font creation application too - the seven weights are interpolated between just a few “masters”. Although the interpolation is carried out automatically, so I never have to think about it.)

1 Like

Using the parametric BSplines from the same Interpolations.jl package might be of interest for this problem too:

using Interpolations, Plots
t = 0:.2:1
x, y = 2sin.(π*t), cos.(π*t)
itp = Interpolations.scale(interpolate([x y], (BSpline(Cubic(Natural(OnGrid()))), NoInterp())), t, 1:2)
tfine = 0:.01:1
xs, ys = [itp(t,1) for t in tfine], [itp(t,2) for t in tfine]
x0, y0 = itp(0.5,1), itp(0.5,2) # interpolate point at t=0.5
plot(xs, ys, aspect_ratio=1, label="BSpline", title="Interpolations.jl parametric BSpline")
scatter!(x, y, label="input points")
scatter!([x0], [y0], ms=5, mc=:red, label="Interpolated") 

Interpolations_parametric_BSpline

2 Likes

The answers so far pass exactly through all known points, which is not what you originally drew.

There are alternatives using eg least squares fitting instead of interpolation that would give something closer to your original drawing, if that would be of interest.

3 Likes

That’s an interesting point! As usual there’s a lot more to it than a non-mathematician would expect… :slight_smile: )

1 Like

What do these “few equally-spaced values between 0.0 and 1.0” represent?
(by “equally-spaced”, I assume these are y-values that go with equidistant xs)

There’s an implementation of the Hobby algorithm for TikZ: http://mirrors.ctan.org/graphics/pgf/contrib/hobby/hobby.pdf that I liked a lot when I was trying to define a naturally-looking curve by specifying as few points as possible. It would be great to have something like that for Julia (I don’t think anyone has implemented it yet). But I don’t know how the position on the curve is parameterized, which is essential for your application.

2 Likes

These are color channel values, eg red has value 0.85 at 0.5…

for animating color change?

Yes, smoothly… :honey_pot:

@dpsanders, in that situation Paul Dierckx is our friend:

using Dierckx, Plots
t = 0.05:.2:0.95; rt = rand(length(t));
x, y = 2sin.(π*t) .+ 0.3rt, cos.(π*t) .+ 0.2rt
spl = ParametricSpline(t, [x y]', bc="extrapolate", s = 0.1)
tfine = 0:.01:1
xys = evaluate(spl,tfine);
xy0 = evaluate(spl, 0.5)  # interpolate point at t=0.5
plot(xys[1,:], xys[2,:], aspect_ratio=1, label="Dierckx.jl parametric spline", title="Dierckx parametric spline")
scatter!(x, y, label="input points", xlimits = (-1.5,2.5))
scatter!([xy0[1]], [xy0[2]], ms=5, mc=:red, label="Interpolated", legend=:bottomleft) 

Dierckx_parametric_spline

5 Likes

So far it’s working well:

Thanks for the help!

5 Likes

Ah! This is so nice. Sorry, couldn’t hold myself and here is colorscheme maker together with palette extraction

using UrlDownload, ParallelKMeans
using ImageMagick
using ImageCore
using StatsBase

function extract_palette(url, k)
    img = urldownload(url, parser = ImageMagick.load ∘ IOBuffer)
    points = Float64.(channelview(vec(img)))
    res = kmeans(Hamerly(), points, k)
    palette = map(1:k) do i
        RGB(res.centers[:, i]...)
    end
    cnts = sort([x for x in countmap(res.assignments)], by = x -> x[2], rev = true)
    cnts = map(x -> x[1], cnts)
    palette[cnts]
end

And this is how it looks together

4 Likes

Nice work! A few years ago I spent some hours getting something similar to that working:

… you make it look easy!

I was never convinced that the clustering was doing the right thing - I gather the process is not deterministic, but I sometimes thought there were colors in the result that weren’t in the original… :slight_smile:

2 Likes

Never done it myself, but I was told that color clusterization should be done in other colorspaces, not in RGB (maybe it’s already done in ColorSchemeTools). Regarding clusterization, ordering is different, but clustering more or less the same for different runs.

L*a*b (CIELAB) works well for this.

1 Like

This is what I need a one to many function graph!