Find an array inside a matrix with tolerance - python

Summary
Using numpy, I'm trying to find the value of a pixel (expressed as [r,g,b]) in a matrix which size is N by 3; so I want to find the row where the array is but I want to add a tolerance because it can happen that it does not match exactly.
With np.all (see raw below) it is possible to do this but the value should be exactly the same.
result_primo_check = np.all(element_2_find==matrix, axis=1)
Example
The problem is that I have element_2_find = [144, 0, 256] but in matrix the most similar row is [148, 0, 250]. Is there a command that add a tolerance or something similar?

Just compute whatever distance (e.g. Euclidean) you want to use between your pixel and the rest of the image and select the image location that matches most closely (perhaps only if the distance is below some threshold).
import numpy as np
img = np.random.rand(100, 3)
pixel = np.random.rand(1, 3)
dists = ((img - pixel) ** 2).sum(-1)
min_idx = np.unravel_index(dists.argmin(), dists.shape)
min_dist = dists[min_idx]
# img[min_idx] is the closest pixel in the image to your target pixel
print(min_idx, min_dist, img[min_idx])

Related

Generate pixel density map (heatmap) from image with numpy array manipulation

The specific problem I try to solve is:
I have a binary image binary map that I want to generate a heatmap (density map) for, my idea is to get the 2D array of this image, let say it is 12x12
a = np.random.randint(20, size=(12, 12));
index and process it with a fixed-size submatrix (let say 3x3), so for every submatrix, a pixel percentage value will be calculated (nonzero pixels/total pixel).
submatrix = a[0:3, 0:3]
pixel_density = np.count_nonzero(submatrix) / submatrix.size
At the end, all the percentage values will made up a new 2D array (a smaller, 4x4 density array) that represent the density estimation of the original image. Lower resolution is fine because the data it will be compared to has a lower resolution as well.
I am not sure how to do that through numpy, especially for the indexing part. Also if there is a better way for generating heatmap like that, please let me know as well.
Thank you!
Maybe a 2-D convolution? Basically this will sweep through the a matrix with the b matrix, which is just 1s below. So it will do the summation you were looking for. This link has a visual demo of convolution near the bottom.
import numpy as np
from scipy import signal
a = np.random.randint(2, size=(12, 12))
b = np.ones((4,4))
signal.convolve2d(a,b, 'valid') / b.sum()

extracting the local maxima (coordinates) from a 2d likelihood matrix (numpy array) efficiently

I'm trying to extract the coordinates of the local maxima from a numpy 2D matrix. the values are numbers between 0 and 1 representing a likelihood that an object is in that location.
I've tried threshold-ing the matrix and extracting the argmax and saving the coordinates and changing it's value to 0 and looping until the threshold encountered.
detections = []
while True:
maxloc = np.unravel_index(np.argmax(scmap),
scmap.shape)
if scmap[maxloc] > 0.9:
# other code ..
detections.append(maxloc)
scmap[maxloc] = 0
# after that, what i did is calculating the euclidean distance
# between each pair and excluded the ones that does not meet the
# threshold
I am not satisfied with this, and i think there is more efficient elegant ways to extract the local maxima. thoughts ?
Locating local maxima is a built-in feature of scikit-image which locates values that are maximal within some predefined distance.
from skimage.feature import peak_local_max
coordinates = peak_local_max(scmax, min_distance=5)
I'm not sure how this is actually implemented but one implementation method is perform non-maximal suppression (i.e. Iterate through each value in the matrix and compare with all values within a radius. If the value is not maximal in that window then set it to some predefined value like zero or -inf). Then take the coordinates of all non-suppressed values (possibly above some threshold) as the collection of local maxima.
If you are trying to extract coordinates of a Numby Matrix, of all of the values that meets a certain threshold, you can simply compare the threshold to the entire matrix.
import numpy as np
data = np.array([
[0, 0.5, 0.95],
[0, 0.95, 0.5],
[0.95, 0.5, 0]
])
thresholded_coordinates = np.argwhere(data > 0.9)
# array([[0, 2], [1,1], [2, 0]])
The output of the thresholded_coordinates is a pairwise collection of coordinates. (0,2) indicates it is the third value in the first row (0-indexed). The output is indicated on the comment on the last line.

Interpolate Image for given indices python

I've an image of about 8000x9000 size as a numpy matrix. I also have a list of indices in a numpy 2xn matrix. These indices are fractional as well as may be out of image size. I need to interpolate the image and find the values for the given indices. If the indices fall outside, I need to return numpy.nan for them. Currently I'm doing it in for loop as below
def interpolate_image(image: numpy.ndarray, indices: numpy.ndarray) -> numpy.ndarray:
"""
:param image:
:param indices: 2xN matrix. 1st row is dim1 (rows) indices, 2nd row is dim2 (cols) indices
:return:
"""
# Todo: Vectorize this
M, N = image.shape
num_indices = indices.shape[1]
interpolated_image = numpy.zeros((1, num_indices))
for i in range(num_indices):
x, y = indices[:, i]
if (x < 0 or x > M - 1) or (y < 0 or y > N - 1):
interpolated_image[0, i] = numpy.nan
else:
# Todo: Do Bilinear Interpolation. For now nearest neighbor is implemented
interpolated_image[0, i] = image[int(round(x)), int(round(y))]
return interpolated_image
But the for loop is taking huge amount of time (as expected). How can I vectorize this? I found scipy.interpolate.interp2d, but I'm not able to use it. Can someone explain how to use this or any other method is also fine. I also found this, but again it is not according to my requirements. Given x and y indices, these generated interpolated matrices. I don't want that. For the given indices, I just want the interpolated values i.e. I need a vector output. Not a matrix.
I tried like this, but as said above, it gives a matrix output
f = interpolate.interp2d(numpy.arange(image.shape[0]), numpy.arange(image.shape[1]), image, kind='linear')
interp_image_vect = f(indices[:,0], indices[:,1])
RuntimeError: Cannot produce output of size 73156608x73156608 (size too large)
For now, I've implemented nearest-neighbor interpolation. scipy interp2d doesn't have nearest neighbor. It would be good if the library function as nearest neighbor (so I can compare). If not, then also fine.
It looks like scipy.interpolate.RectBivariateSpline will do the trick:
from scipy.interpolate import RectBivariateSpline
image = # as given
indices = # as given
spline = RectBivariateSpline(numpy.arange(M), numpy.arange(N), image)
interpolated = spline(indices[0], indices[1], grid=False)
This gets you the interpolated values, but it doesn't give you nan where you need it. You can get that with where:
nans = numpy.zeros(interpolated.shape) + numpy.nan
x_in_bounds = (0 <= indices[0]) & (indices[0] < M)
y_in_bounds = (0 <= indices[1]) & (indices[1] < N)
bounded = numpy.where(x_in_bounds & y_in_bounds, interpolated, nans)
I tested this with a 2624x2624 image and 100,000 points in indices and all told it took under a second.

Is there a way to stack multiple 2D (numpy) image arrays about a specified point?

I need to stack many images that are represented by 2D numpy arrays of the same shape (i.e., take the sum or the median of them all). However, as I stack them, they need to be aligned properly -- each image, while the same shape, is all black with a small circular object around the center, but not exactly at the center. I can find the coordinates of the centroid for each image (using the module SourceProperties.centroid through the package photutils), but these coordinates will be different for each image -- they are also subpixel coordinates (example: (y, x) = (203.018, 207.397)).
I do not know of a way to simply move the objects to the center of the arrays, given the centroids have subpixel coordinates, so it seems like it would be more straightforward if there was a way to align each one by their unique centroid coordinates as I stack them... in other words:
import numpy as np
# First image = array1, shape = (400, 400)
centroid1 = (203.018, 207.397)
# Second image = array2, shape = (400, 400)
centroid2 = (205.256, 199.312)
array_list = [array1, array2]
>>> stacked = np.median(array_list, axis=0) # but while setting centroid1 = centroid2 so that the two centroid points exactly overlap while computing median
But I'm not really sure how this would look in code. Is this possible?
Step 1: ignore the subpixel/fractional part, as it makes no sense for arrays. An array cannot be shifted by 0.34 elements to the right.
Step 2: roll arrays to place the centroids consistently.
Step 3: stack them.
As illustrated by the code below, which places centroids in the geometric center of the array.
centroid1 = (203.018, 207.397)
centroid2 = (205.256, 199.312)
centroid1 = np.round(centroid1).astype(np.int)
centroid2 = np.round(centroid2).astype(np.int)
center = np.array(array1.shape)//2
array1_rolled = np.roll(array1, center-centroid1, (0, 1))
array2_rolled = np.roll(array2, center-centroid2, (0, 1))
array_list = [array1_rolled, array2_rolled]
stacked = np.median(array_list, axis=0)

Numpy filter pixel indices

I want to extract the sum count of all pixels of an RGB image where R=0 and B=0 and where the x,y coordinates of those pixels are lying on the border of an image.
First I get the coordinates of the pixels with R=0 and B=0:
import cv2
import numpy as np
i = cv2.imread("test2.png")
indices = np.where((i[:, :, 0] == 0) & (i[:, :, 2] == 0))
Which gives me a list of the coordinates. Now I want to get the sum of all pixels where the x position is 0 or the image width (in this case 21).
I could sort the list but I would like to stick to numpy arrays if possible. Is there an fancy way to do it?
Approach #1
With X along the second axis, here's one fancy way -
(i[...,[0,2]]==0).all(-1)[:,[0,-1]].sum()
Approach #2
With multi-dim indexing -
(i[:,[0,-1],[0,2]]==0).sum()
Approach #3
For performance, use more of slicing -
mask = (i[...,0]==0) & (i[...,2]==0)
out_x = (mask[:,0] + mask[:,-1]).sum()
On older NumPy versions, np.count_nonzero might be better than .sum().

Categories