Linear resampling datapoints captured at fluctuating time intervals, to flxed time intervals, in swift

北慕城南 提交于 2019-12-07 00:17:26

I don't understand the math you want to do 100%, but I do understand how to use Accelerate. I created a function which makes it easier to call this Accelerate function and shows you how it works.

/**
 Vector linear interpolation between neighboring elements

 - Parameter a: Input vector.
 - Parameter b: Input vector: integer parts are indices into a and fractional parts are interpolation constants.

 Performs the following operation:

 ```C
 for (n = 0; n < N; ++n) {
    double b = B[n];
    double index = trunc([b]); //int part of B value
    double alpha = b - index; //frac part of B value

    double a0 = A[(int)index];     //indexed A value
    double a1 = A[(int)index + 1]; //next A value

    C[n] = a0 + (alpha * (a1 -a0)); //interpolated value
 }
 ```
 Generates vector C by interpolating between neighboring values of vector A as controlled by vector B. The integer portion of each element in B is the zero-based index of the first element of a pair of adjacent values in vector A.

 The value of the corresponding element of C is derived from these two values by linear interpolation, using the fractional part of the value in B.
*/
func interpolate(inout a: [Double], inout b: [Double]) -> [Double] {
    var c = [Double](count: b.count, repeatedValue: 0)
    vDSP_vlintD(&a, &b, 1, &c, 1, UInt(b.count), UInt(a.count))
    return c
}

EDIT: Alright, I wrapped my head around your problem, I understand now what you want to do. Was pretty fun to do, I came up with this:

import Accelerate

func calculateB(sampleTimes: [Double], outputTimes: [Double]) -> [Double] {
    var i = 0
    return outputTimes.map { (time: Double) -> Double in
        defer {
            if time > sampleTimes[i] { i++ }
        }
        return Double(i) + (time - sampleTimes[i]) / (sampleTimes[i+1] - sampleTimes[i])
    }
}

func interpolate(inout b: [Double], inout data: [Double]) -> [Double] {
    var c = [Double](count: b.count, repeatedValue: 0)
    vDSP_vlintD(&data, &b, 1, &c, 1, UInt(b.count), UInt(data.count))
    return c
}


let sampleTimes : [Double] = [0.0, 1.3, 2.2, 3.4, 4.2, 5.5, 6.6, 7.2, 8.4, 9.5, 10.0]
let outputTimes : [Double] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

var metric_1 : [Double] = [4, 3, 6, 7, 4, 5, 7, 4, 2, 7, 2]
var metric_2 : [Double] = [5, 4, 7, 5, 6, 6, 1, 3, 1, 6, 7]
var metric_3 : [Double] = [9, 8, 5, 7, 4, 8, 5, 6, 8, 9, 5]

var b = calculateB(sampleTimes, outputTimes: outputTimes)

interpolate(&b, data: &metric_1)   // [4, 3.230769, 5.333333, 6.666667, 4.75, 4.615385, 5.909091, 5, 2.666667, 4.727273, 2]
interpolate(&b, data: &metric_2)   // [5, 4.230769, 6.333333, 5.666667, 5.75, 6, 3.727273, 2.333333, 1.666667, 3.727273, 7]
interpolate(&b, data: &metric_3)   // [9, 8.230769, 5.666667, 6.333333, 4.75, 6.461538, 6.636364, 5.666667, 7.333333, 8.545455, 5]

The vars are necessary for Accelerate. I don't know how calculateB could be done with Accelerate, I mean it's possible I think, but it's a pain to search for the correct vDSP functions...

Here's another solution without using any of the Accelerate stuff

public class Resampler {


///
/// ### Class method to resample some data
///
/// ### Inputs
/// - Actual time data that may not be regularly sampled
/// - Desired times you want the metric found at
/// - Metric data corresponding with actual time data
///
public class func resample( acualTimes atimes: [Double], desiredTimes dtimes: [Double], metric: [Double] ) ->[Double]
{
    //
    // Initialize the desired metrics array
    //
    var desiredMetrics: [Double] = [Double](count: dtimes.count, repeatedValue: 0);

    // Initialize a counter to keep track of which metric value we are on
    var counter: Int = 0;

    // Loop through the desired times
    for dtime in dtimes {

        // Find the bounding indices, based on actual time data, for the desired time
        // using a binary search
        let (li, ri) = binarySearch(0,highBound: atimes.count-1, desiredTime: dtime, timeData: atimes);

        // Find the desired metric using an interpolation
        desiredMetrics[counter] = linearInterpolate(lowTime: atimes[li],
                                                    highTime: atimes[ri],
                                                    lowMetric: metric[li],
                                                    highMetric: metric[ri],
                                                    desiredTime: dtime);
        // Increment the counter
        counter++;
    }

    // Return the desired metrics
    return desiredMetrics;
}


///
/// ### Binary search code to find the bounding time value indices
///
private class func binarySearch(  lowBound: Int,
                            highBound: Int,
                            desiredTime: Double,
                            timeData: [Double]) -> (leftIndex: Int, rightIndex: Int)
{
    if( highBound-lowBound == 1 ){
        return (lowBound, highBound);
    }else{
        let center: Int = (lowBound + highBound)/2;
        if( desiredTime <= timeData[center]){
            return binarySearch(lowBound, highBound: center, desiredTime: desiredTime, timeData: timeData);
        }else{
            return binarySearch(center, highBound: highBound, desiredTime: desiredTime, timeData: timeData);
        }
    }
}


///
/// ### Linear interpolation method
///
private class func linearInterpolate(   lowTime lt: Double,
                                        highTime ht: Double,
                                        lowMetric lm: Double,
                                        highMetric hm: Double,
                                        desiredTime dt: Double ) -> Double
{
    return lm + (dt-lt)*(hm-lm)/(ht-lt);
}

}

And then you can run it by just doing the following:

    let times: [Double] = [0.0,1.3,2.2,3.4,4.2,5.5,6.6,7.2,8.4,9.5,10.0];
    let desiredTimes: [Double] = [0,1,2,3,4,5,6,7,8,9,10];
    let metricData: [Double] = [4,3,6,7,4,5,7,4,2,7,2];

    let desiredMetrics = Resampler.resample(acualTimes: times, desiredTimes: desiredTimes, metric: metricData);
    print(desiredMetrics)

This example above will output:

[4.0, 3.23076923076923, 5.33333333333333, 6.66666666666667, 4.75, 4.61538461538461, 5.90909090909091, 5.0, 2.66666666666667, 4.72727272727273, 2.0]

Another solution to the problem, 100% Accelerate:

import Accelerate

let metric_1: [Double] = [4.0, 3.0, 6.0, 7.0, 4.0, 5.0, 7.0, 4.0, 2.0, 7.0, 2.0]
let original_times: [Double] = [0.0, 1.3, 2.2, 3.4, 4.2, 5.5, 6.6, 7.2, 8.4, 9.5, 10.0]
let wanted_times: [Double] = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]

let count = metric_1.count
let length = vDSP_Length(count)
var output = [Double](repeating: 0, count: count)
var interpolationConstant = 2.0

// calculate interpolated times
vDSP_vintbD(original_times, 1, wanted_times, 1, &interpolationConstant, &output, 1, length)

// calculate interpolated values
vDSP_vlintD(metric_1, &output, 1, &output, 1, length, length)

output: [4.0,
         3.2999999999999998,
         5.3999999999999995,
         6.5999999999999996,
         4.6000000000000005,
         4.5,
         5.8000000000000007,
         4.6000000000000005,
         2.8000000000000007,
         4.5,
         2.0]
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!