How to find the first extremum in 2D array of x,y points?

Viewed 128

I have a set of points x_i, y_i which represents not bijective mapping (there is no one to one correspondence. See the attached picture: enter image description here

(Don't pay attention on the second line. It just shows a center mass). I am trying to find the first peak on it (as you can see it finds not correctly). The code is given below. Here I am sorting points by Ox axis, then using find_peaks function:

# sort points by X axis
aa = zip(x,y)
bb = sorted(aa, key=lambda x: (x[0], x[1]))
x,y = zip(*bb)
x = np.array(x)
y = np.array(y)
# find all peaks
peaks, props = find_peaks(y, prominence=0.01, height=0.4)
print('rmax candidates=', y[peaks])
rmax = y[peaks[0]] # first peak is not correct

I noticed that the here sorting processed incorrectly. If I plot only y array, then I see the picture: enter image description here. Where we see 'a gear with very sharp teeth'.

Thus, how to sort points (if I can set a starting point) in the closest way. For a human being draw a line through the graph is an easy task, but how to develop an algorithm for a computer? I know that a similar algorithm is already used in Online Digitizer. Where point coordinates from graphs can be easily extracted.

Maybe you have better algorithm for finding the first peak? If you have any questions, please, ask me.

The data can be found here:

1 Answers

The following idea can be given:

Read the file and put data into a 2D NumPy array.

arr = []        
with open(str(Path('example.csv'))) as csv_file:
    reader = csv.reader(csv_file, delimiter=',')
    for row in reader:
        arr.append([float(row[0]),float(row[1])])
arr = np.array(arr)

Since the data is unordered then when we connect points subsequently, we observe an ugly graph.

fig0 = go.Figure(go.Scatter(x=arr[:,0],y=arr[:,1], mode='lines'))
fig0.show()

enter image description here

To get an appropriate picture we need to sort points from some starting point in such a way that only the nearest points are connected

arx, x0, xmin, xmax = sort_by_nearest(arr)
x = arx[:,0]
y = arx[:,1]

After sorting the data y looks like: enter image description here And the not bijective behavior is gone, and we can easily find the first peak for only y data using find_peaks function. prominence is the minimal height of descent and ascent from a peak to the next peak. Thus, small noisy peaks will be discarded. height is responsible for a parameter where peaks will be searched only in regions where y>height

peaks, props = find_peaks(y, prominence=0.01, height=0.4)

Here we have several candidates

print('rmax candidates=', y[peaks])

Of course, we take the first one

rmax = y[peaks[0]]
x_rmax = x[peaks[0]] - x0

Let's draw the result: enter image description here

fig0 = go.Figure(go.Scatter(y=arx[:,1], mode='lines'))
fig0.show()

fig0 = go.Figure(go.Scatter(x=arx[:,0]-x0, y=arx[:,1], name='my_line', mode='lines'))
fig0.add_trace(go.Scatter(x=[x_rmax,x_rmax], y=[0,0.5], name='peak coord', mode='lines', line=dict(color='black', width=2, dash='dot')))

fig0.show()

Let's focus on the algorithm for sorting data by neighbors.

import pandas as pd
import numpy as np
import plotly.graph_objects as go
import csv
from scipy.signal import find_peaks
import scipy.spatial.distance as ds
from pathlib import Path

A function for searching the closest point is tricky, we need to somehow flag points which are already observed to avoid infinite cycles. We do it just by modifying point coords by big number a copy of the array arr_full

def closest_point(arr_full, xi, yi, inds):
    N = arr_full.shape[0]
    arr_full[inds] = 1e+30
    dist = (arr_full[:, 0] - xi)**2 + (arr_full[:, 1] - yi)**2
    i_min = dist.argmin()
    #returns an index of the nearest point and its coordinate
    return i_min, arr_full[i_min][0], arr_full[i_min][1]

def sort_by_nearest(arr):
    N = arr.shape[0]
    nearest_point = [None]*N
    #find initial point
    xmin = min(arr[:,0])
    xmax = max(arr[:,0])
    # we copy the original array
    arr_cut = np.copy(arr)
    # the area in which we are 100% sure in absence of the starting point we flag by big value
    arr_cut[arr[:,0] > 0.5*(xmin + xmax)] = 10e+30
    iymin = arr_cut[:,1].argmin()
    # the staring points are
    x0, y0 = arr[iymin, 0], arr[iymin, 1]
    # we initialize the sorted value nearest_point
    nearest_point[0] = [arr[iymin,0],arr[iymin,1]]
    print('Starting point:', x0, y0)
    # we put in it the indices of visited points
    observed = [iymin]
    i_min = iymin
    for i in range(1,N):
        xi, yi = arr[i_min]
        i_min, xip, yip = closest_point(arr, xi, yi, i_min)
        nearest_point[i] = [xip, yip]
        observed.append(i_min)
    nearest_point = np.array(nearest_point)
    return np.array(nearest_point), x0, xmin, xmax
Related