I have written a function (given below) to find positions of top num_peaks intensity values in an image along with performing a non-maximum suppression to select only local maxima values:
def find_peaks(img, num_peaks, threshold_fraction=0.5, nhood_size=None):
"""Find locally maximum intensities in an image"""
# calculate threshold as a fraction of intensity range in the image
threshold = (threshold_fraction * (img.max() - img.min())) + img.min()
# determine offsets in each direction that constitute a neighbourhood
if nhood_size is None:
nhood_size = np.array(img.shape) * 0.02
nhood_offset = (np.around(nhood_size / 2)).astype(int)
# create array with labelled fields to allow intensity-specific sorting
rows, cols = np.array(np.where(img >= threshold))
values = []
for i, j in zip(rows, cols):
values.append((i, j, img[i, j]))
dtype = [('row', int), ('col', int), ('intensity', np.float64)]
indices = np.array(values, dtype=dtype)
# sort in-place in descending order
indices[::-1].sort(order='intensity')
# Perform suppression
for idx_set in indices:
intensity = idx_set[2]
if not intensity:
continue
x0 = idx_set[1] - nhood_offset[1]
xend = idx_set[1] + nhood_offset[1]
y0 = idx_set[0] - nhood_offset[0]
yend = idx_set[0] + nhood_offset[0]
indices_to_suppress = np.where((indices['col'] >= x0) &
(indices['col'] <= xend) &
(indices['row'] >= y0) &
(indices['row'] <= yend))
if indices_to_suppress:
indices['intensity'][indices_to_suppress] = 0
idx_set[2] = intensity
# perform second sorting & return the remaining n (num_peaks) most
# intense values
indices[::-1].sort(order='intensity')
if len(indices) <= num_peaks:
return np.array([np.array([i, j])
for i, j in zip(indices['row'], indices['col'])])
# or return all of them
return np.array([np.array([i, j])
for i, j in zip(indices['row'][:num_peaks], indices['col'][:num_peaks])])
This seems work properly for small images and a large threshold_fraction (less values to suppress around), but has proven to be quite inefficient for my purposes, where I have lower thresholds like 0.1 to 0.2. I have not been able to make this more efficient with my beginner numpy skills.
I would like to know if I could make any changes to this piece of code that might improve its performance. Also, since I'm using numpy and OpenCV, it would be nice to know if there was a library function(s) that could achieve something similar or utilize it somehow to write an efficient peak finder.