I have data that always looks something like this:
alt text http://michaelfogleman.com/static/images/chart.png
I need an algorithm to locate the three peaks.
The x-axis is actually a camera position and the y-axis is a measure of image focus/contrast at that position. There are features at three different distances that can be in focus and I need to determine the x-values for these three points.
The middle hump is always a little harder to pick out, even for a human.
I have a homemade algorithm that mostly works, but I'm wondering if there's a standard way to grab local maxima from a function that can have a little noise in it. The peaks overcome the noise easily though.
Also, being camera data, an algorithm that doesn't require scanning the full range could be useful.
Edit: Posting the Python code that I ended up using. It uses my original code that finds maxima given a search threshold and does a binary search to find a threshold that results in the desired number of maxima.
Edit: Sample data included in code below. New code is O(n) instead of O(n^2).
def find_n_maxima(data, count):
low = 0
high = max(data) - min(data)
for iteration in xrange(100): # max iterations
mid = low + (high - low) / 2.0
maxima = find_maxima(data, mid)
if len(maxima) == count:
return maxima
elif len(maxima) < count: # threshold too high
high = mid
else: # threshold too low
low = mid
return None # failed
def find_maxima(data, threshold):
def search(data, threshold, index, forward):
max_index = index
max_value = data[index]
if forward:
path = xrange(index + 1, len(data))
else:
path = xrange(index - 1, -1, -1)
for i in path:
if data[i] > max_value:
max_index = i
max_value = data[i]
elif max_value - data[i] > threshold:
break
return max_index, i
# forward pass
forward = set()
index = 0
while index < len(data) - 1:
maximum, index = search(data, threshold, index, True)
forward.add(maximum)
index += 1
# reverse pass
reverse = set()
index = len(data) - 1
while index > 0:
maximum, index = search(data, threshold, index, False)
reverse.add(maximum)
index -= 1
return sorted(forward & reverse)
data = [
1263.900, 1271.968, 1276.151, 1282.254, 1287.156, 1296.513,
1298.799, 1304.725, 1309.996, 1314.484, 1321.759, 1323.988,
1331.923, 1336.100, 1340.007, 1340.548, 1343.124, 1353.717,
1359.175, 1364.638, 1364.548, 1357.525, 1362.012, 1367.190,
1367.852, 1376.275, 1374.726, 1374.260, 1392.284, 1382.035,
1399.418, 1401.785, 1400.353, 1418.418, 1420.401, 1423.711,
1425.214, 1436.231, 1431.356, 1435.665, 1445.239, 1438.701,
1441.988, 1448.930, 1455.066, 1455.047, 1456.652, 1456.771,
1459.191, 1473.207, 1465.788, 1488.785, 1491.422, 1492.827,
1498.112, 1498.855, 1505.426, 1514.587, 1512.174, 1525.244,
1532.235, 1543.360, 1543.985, 1548.323, 1552.478, 1576.477,
1589.333, 1610.769, 1623.852, 1634.618, 1662.585, 1704.127,
1758.718, 1807.490, 1852.097, 1969.540, 2243.820, 2354.224,
2881.420, 2818.216, 2552.177, 2355.270, 2033.465, 1965.328,
1824.853, 1831.997, 1779.384, 1764.789, 1704.507, 1683.615,
1652.712, 1646.422, 1620.593, 1620.235, 1613.024, 1607.675,
1604.015, 1574.567, 1587.718, 1584.822, 1588.432, 1593.377,
1590.533, 1601.445, 1667.327, 1739.034, 1915.442, 2128.835,
2147.193, 1970.836, 1755.509, 1653.258, 1613.284, 1558.576,
1552.720, 1541.606, 1516.091, 1503.747, 1488.797, 1492.021,
1466.720, 1457.120, 1462.485, 1451.347, 1453.224, 1440.477,
1438.634, 1444.571, 1428.962, 1431.486, 1421.721, 1421.367,
1403.461, 1415.482, 1405.318, 1399.041, 1399.306, 1390.486,
1396.746, 1386.178, 1376.941, 1369.880, 1359.294, 1358.123,
1353.398, 1345.121, 1338.808, 1330.982, 1324.264, 1322.147,
1321.098, 1313.729, 1310.168, 1304.218, 1293.445, 1285.296,
1281.882, 1280.444, 1274.795, 1271.765, 1266.857, 1260.161,
1254.380, 1247.886, 1250.585, 1246.901, 1245.061, 1238.658,
1235.497, 1231.393, 1226.241, 1223.136, 1218.232, 1219.658,
1222.149, 1216.385, 1214.313, 1211.167, 1208.203, 1206.178,
1206.139, 1202.020, 1205.854, 1206.720, 1204.005, 1205.308,
1199.405, 1198.023, 1196.419, 1194.532, 1194.543, 1193.482,
1197.279, 1196.998, 1194.489, 1189.537, 1188.338, 1184.860,
1184.633, 1184.930, 1182.631, 1187.617, 1179.873, 1171.960,
1170.831, 1167.442, 1177.138, 1166.485, 1164.465, 1161.374,
1167.185, 1174.334, 1186.339, 1202.136, 1234.999, 1283.328,
1347.111, 1679.050, 1927.083, 1860.902, 1602.791, 1350.454,
1274.236, 1207.727, 1169.078, 1138.025, 1117.319, 1109.169,
1080.018, 1073.837, 1059.876, 1050.209, 1050.859, 1035.003,
1029.214, 1024.602, 1017.932, 1006.911, 1010.722, 1005.582,
1000.332, 998.0721, 992.7311, 992.6507, 981.0430, 969.9936,
972.8696, 967.9463, 970.1519, 957.1309, 959.6917, 958.0536,
954.6357, 954.9951, 947.8299, 953.3991, 949.2725, 948.9012,
939.8549, 940.1641, 942.9881, 938.4526, 937.9550, 929.6279,
935.5402, 921.5773, 933.6365, 918.7065, 922.5849, 939.6088,
911.3251, 923.7205, 924.8227, 911.3192, 936.7066, 915.2046,
919.0274, 915.0533, 910.9783, 913.6773, 916.6287, 907.9267,
908.0421, 908.7398, 911.8401, 914.5696, 912.0115, 919.4418,
917.0436, 920.5495, 917.6138, 907.5037, 908.5145, 919.5846,
917.6047, 926.8447, 910.6347, 912.8305, 907.7085, 911.6889,
]
for n in xrange(1, 6):
print 'Looking for %d maxima:' % n
indexes = find_n_maxima(data, n)
print indexes
print ', '.join(str(data[i]) for i in indexes)
print
Output:
Looking for 1 maxima:
[78]
2881.42
Looking for 2 maxima:
[78, 218]
2881.42, 1927.083
Looking for 3 maxima:
[78, 108, 218]
2881.42, 2147.193, 1927.083
Looking for 4 maxima:
[78, 108, 218, 274]
2881.42, 2147.193, 1927.083, 936.7066
Looking for 5 maxima:
[78, 108, 218, 269, 274]
2881.42, 2147.193, 1927.083, 939.6088, 936.7066
Check the middle element of the array, if it is greater than the elements following it and the element preceding it, then it is the local maxima, else if it is greater than the preceding element, then the local maxima is in the left half, else the local maxima is in the right half.
Approach: The idea is to iterate over the given array arr[] and check if each element of the array is smallest or greatest among their adjacent element. If it is smallest then it is local minima and if it is greatest then it is local maxima.
Examples on Local Maximum and Minimum Example 1: Find the local maxima and local minima of the function f(x) = 2x3 + 3x2 - 12x + 5., using the first derivative test. Hence the limiting points are x = 1, and x = -2. Let us take the points in the immediate neighbourhood of x = 1. The points are {0, 2}.
The local maxima would be any x point which has a higher y value than either of its left and right neighbors. To eliminate noise, you could put in some kind of tolerance threshold (ex. x point must have higher y value than n of its neighbors).
To avoid scanning every point, you could use the same approach but go 5 or 10 points at a time to get a rough sense of where the maximum lie. Then come back to those areas for a more detailed scan.
Couldn't you move along the graph, regularly calculating the derivative and if it switches from positive to negative you've found a peak?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With