Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Array interpolation

Before I start, please accept my apologies that I'm not a mathematician and don't really know the proper names for what I'm trying to do... ;) Pointers to any plain-English explanations that might help would be most appreciated (as I'm purely Googling at the moment based upon what I think the solution might be).

If have a multi-dimensionsal array of source values and wanted to upscale that array by a factor of n, I think that what I'd need to use is Bicubic Interpolation Certainly the image top right on that page is representative of what I'm aiming for - creating a graduated flow of values between the underlying source data points, based upon the value(s) of their surrounding neighbours. I completely accept that by not increasing the volume of data I am not increasing the resolution of the data; merely blurring the edges.

Something akin to going from this;

Source grid

to this (and beyond);

Target grid

Following the link within the Wikipedia article gives me a (supposed) example implementation of what I'm striving for, but if it does I fear I'm currently missing the logical leap to get myself there. When I call getValue(source, 0.5, 0.5) on the BicubicInterpolator, what am I getting back? I thought that if I gave an x/y of (0.0,0.0) I would get back the bottom-left value of the grid and if I looked at (1,1) I would get top-right, and any value between would give me the specified position within the interpolated grid.

double[][] source  = new double[][] {{1, 1, 1, 2}, {1, 2, 2, 3}, {1, 2, 2, 3}, {1, 1, 3, 3}};
BicubicInterpolator bi = new BicubicInterpolator();
for (double idx = 0; idx <= 1; idx += 0.1) {
  LOG.info("Result (" + String.format("%3.1f", idx) + ", " + String.format("%3.1f", idx) + ") : " + bi.getValue(source, idx, idx));         
}

The output for a diagonal line across my source grid however is;

Result (0.0, 0.0) : 2.0
Result (0.1, 0.1) : 2.08222625
Result (0.2, 0.2) : 2.128
Result (0.3, 0.3) : 2.13747125
Result (0.4, 0.4) : 2.11424
Result (0.5, 0.5) : 2.06640625
Result (0.6, 0.6) : 2.00672
Result (0.7, 0.7) : 1.9518312500000001
Result (0.8, 0.8) : 1.92064
Result (0.9, 0.9) : 1.93174625
Result (1.0, 1.0) : 2.0

I'm confused because the diagonals go from 1 to 3 and from 1 to 2; there's nothing going from 2 to 2 with very little (overall) variation. Am I completely mis-understanding things?


EDIT : Following Peter's suggestion to expand the boundary for analysis, the grid can now be generated as a quick'n'dirty upscale to a 30x30 matrix;

Grid from Peter's answer

Now that what's going on is making a bit more sense, I can see that I need to consider a few additional things;

  • Control the overshoot (seen in the middle of the grid where the source has a block of four cells with a value of 2, but the interpolated value peaks at 2.2)
  • Cater for blank values in the source grid and have them treated as blanks, rather than zero, so that they don't skew the calculation
  • Be prepare to be told I'm on a fool's errand and that a different solution is needed
  • See if this was what the customer thought they actually wanted when they said "make it less blocky"
like image 299
Stu.C Avatar asked Mar 12 '12 14:03

Stu.C


1 Answers

If you assume the "outside" temperature is the same as the outer most ring of values, and you want to shift which port of the grid you are considering...

public static void main(String... args) {
    double[][] source = new double[][]{{1, 1, 1, 2}, {1, 2, 2, 3}, {1, 2, 2, 3}, {1, 1, 3, 3}};
    BicubicInterpolator bi = new BicubicInterpolator();
    for (int i = 0; i <= 30; i++) {
        double idx = i / 10.0;
        System.out.printf("Result (%3.1f, %3.1f) : %3.1f%n", idx, idx, bi.getValue(source, idx, idx));
    }
}

public static class CubicInterpolator {
    public static double getValue(double[] p, double x) {
        int xi = (int) x;
        x -= xi;
        double p0 = p[Math.max(0, xi - 1)];
        double p1 = p[xi];
        double p2 = p[Math.min(p.length - 1,xi + 1)];
        double p3 = p[Math.min(p.length - 1, xi + 2)];
        return p1 + 0.5 * x * (p2 - p0 + x * (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3 + x * (3.0 * (p1 - p2) + p3 - p0)));
    }
}

public static class BicubicInterpolator extends CubicInterpolator {
    private double[] arr = new double[4];

    public double getValue(double[][] p, double x, double y) {
        int xi = (int) x;
        x -= xi;
        arr[0] = getValue(p[Math.max(0, xi - 1)], y);
        arr[1] = getValue(p[xi], y);
        arr[2] = getValue(p[Math.min(p.length - 1,xi + 1)], y);
        arr[3] = getValue(p[Math.min(p.length - 1, xi + 2)], y);
        return getValue(arr, x+ 1);
    }
}

prints

Result (0.0, 0.0) : 1.0
Result (0.1, 0.1) : 1.0
Result (0.2, 0.2) : 1.0
Result (0.3, 0.3) : 1.1
Result (0.4, 0.4) : 1.1
Result (0.5, 0.5) : 1.3
Result (0.6, 0.6) : 1.4
Result (0.7, 0.7) : 1.6
Result (0.8, 0.8) : 1.7
Result (0.9, 0.9) : 1.9
Result (1.0, 1.0) : 2.0
Result (1.1, 1.1) : 2.1
Result (1.2, 1.2) : 2.1
Result (1.3, 1.3) : 2.1
Result (1.4, 1.4) : 2.1
Result (1.5, 1.5) : 2.1
Result (1.6, 1.6) : 2.0
Result (1.7, 1.7) : 2.0
Result (1.8, 1.8) : 1.9
Result (1.9, 1.9) : 1.9
Result (2.0, 2.0) : 2.0
Result (2.1, 2.1) : 2.1
Result (2.2, 2.2) : 2.3
Result (2.3, 2.3) : 2.5
Result (2.4, 2.4) : 2.7
Result (2.5, 2.5) : 2.8
Result (2.6, 2.6) : 2.9
Result (2.7, 2.7) : 3.0
Result (2.8, 2.8) : 3.0
Result (2.9, 2.9) : 3.0
Result (3.0, 3.0) : 3.0

Looking at how this works, you have a 2x2 grid of inside values and a 4x4 square outside it for outside values. the (0.0, 0.0) to (1.0, 1.0) values map the diagonal between 2 (in cell 2,2) and 2 (in cell 3,3) using the outer values to help interpolate the values.

double[][] source = new double[][]{{1, 1, 1, 2}, {1, 2, 2, 3}, {1, 2, 2, 3}, {1, 1, 3, 3}};
BicubicInterpolator bi = new BicubicInterpolator();
for (int i = -10; i <= 20; i++) {
    double idx = i / 10.0;
    System.out.printf("Result (%3.1f, %3.1f) : %3.1f%n", idx, idx, bi.getValue(source, idx, idx));
}

prints

Result (-1.0, -1.0) : -5.0
Result (-0.9, -0.9) : -2.8
Result (-0.8, -0.8) : -1.2
Result (-0.7, -0.7) : -0.2
Result (-0.6, -0.6) : 0.5
Result (-0.5, -0.5) : 1.0
Result (-0.4, -0.4) : 1.3
Result (-0.3, -0.3) : 1.5
Result (-0.2, -0.2) : 1.7
Result (-0.1, -0.1) : 1.9
Result (0.0, 0.0) : 2.0
Result (0.1, 0.1) : 2.1
Result (0.2, 0.2) : 2.1
Result (0.3, 0.3) : 2.1
Result (0.4, 0.4) : 2.1
Result (0.5, 0.5) : 2.1
Result (0.6, 0.6) : 2.0
Result (0.7, 0.7) : 2.0
Result (0.8, 0.8) : 1.9
Result (0.9, 0.9) : 1.9
Result (1.0, 1.0) : 2.0
Result (1.1, 1.1) : 2.1
Result (1.2, 1.2) : 2.3
Result (1.3, 1.3) : 2.5
Result (1.4, 1.4) : 2.7
Result (1.5, 1.5) : 2.8
Result (1.6, 1.6) : 2.7
Result (1.7, 1.7) : 2.1
Result (1.8, 1.8) : 0.9
Result (1.9, 1.9) : -1.4
Result (2.0, 2.0) : -5.0
like image 52
Peter Lawrey Avatar answered Oct 20 '22 13:10

Peter Lawrey