Search code examples
pythonarraysinterpolation

How to fill array by keypoints


I have an array like this:
arr = [[180, 210, 240, 270, 300],[38.7, 38.4, 38.2, 37.9,37.7]]
It contains frame numbers from a video and the value of a sensor recorded during that frame.

I have a program to evaluate the video material and need to know the value of the sensor for each frame. However, the data is only recorded in these large steps not to overwhelm the sensor.

How would I go about creating a computationally cheap function, that returns the sensor value for frames which are not listed? The video is not evaluated from the first frame but some unknown offset.

The data should be filled halfway up and down to the next listed frame, e.g. Frames 195 - 225 would all use the value recorded for frame 210. The step is always constant for one video but could vary between videos.
The recording of the sensor starts some time into the video, here 3s in. I wanted to also use that values for all frames before, and similar for the end. So f(0) == f(180) and f(350) == f(300) for this example.

I don't want to do a binary search through the array every time i need a value.
I also thought about filling a second array with single step in a for loop at the beginning of the program, but the array is much larger than the example above. I am worried about memory consumption and again lookup performance.

This is my try at filing the array at the beginning:

sparse_data = [[180, 210, 240, 270, 300],[38.7, 38.4, 38.2, 37.9,37.7]]
delta = sparse_data[0][1] - sparse_data[0][0]
fr_max = sparse_data[0][-1] + delta
fr_min = sparse_data[0][0]
cur_sparse_idx = 1
self.magn_data = np.zeros(fr_max, dtype=np.float32)
    for i in range(fr_max):
        if i <= (fr_min + delta//2):
            self.magn_data[i] = sparse_data[1][0]
        elif i > fr_max: 
            self.magn_data[i] = sparse_data[1][-1]
        else:
            if (i+delta//2) % delta == 0: cur_sparse_idx += 1
            self.magn_data[i] = sparse_data[1][cur_sparse_idx]

Is there a batter way?


Solution

  • You could define a query function to get the value for a specific frame as follows:

    def query(frames: list[int], vals: list[float], frame: int):
        # edge cases, based on the array lengths
        assert len(frames) == len(vals)
        if len(frames) == 1:
            return vals[0]
        if len(frames) == 0:
            return ValueError()
        # arrays have at least length 2
        delta = frames[1] - frames[0]
        start = frames[0]
        end = frames[-1]
        # edge cases for the frame argument
        if frame <= start:
            return vals[0]
        if frame >= end:
            return vals[-1]
        # frame is somewhere "inside" the recorded frames
        frame -= start
        i = frame // delta
        if frame % delta > delta // 2:
            return vals[i + 1]
        else:
            return vals[i]
    

    This function uses constant time and memory. Here are some test cases based on your example, which show how you can use query:

    frames, vals = [[180, 210, 240, 270, 300], [38.7, 38.4, 38.2, 37.9, 37.7]]
    
    assert query(frames, vals, 170) == 38.7
    assert query(frames, vals, 245) == 38.2
    assert query(frames, vals, 196) == 38.4
    assert query(frames, vals, 195) == 38.7