Fast way to recolour an indexed image based on another array - python

I have a an indexed image bins consisting of multiple regions. 0 is background and other positive value is a region.
I want to fill in values for each region based on another array, e.g.:
bins = # image of shape (height, width), type int
ids = np.array([1, 5, ... ]) # region ids
values = np.array([0.1, ...]) # Values for each region, same shape as ids
img = np.empty(bins.shape, 'float32')
img[:] = np.nan
for i, val in zip(ids, values):
img[bins == i + 1] = val
but this loop is super slow in python. Is there a way to write it in a nice numpy way?
Thanks in advance!

Here's an approach -
out = np.take(values, np.searchsorted(ids, bins-1))
out.ravel()[~np.in1d(bins,ids+1)] = np.nan
Please note that this assumes ids to be sorted. If that's not the case, we need to use the optional argument sorter with np.searchsorted.
Here's another one with a very similar idea, but as a minor tweak using initialization and limiting the use of np.searchsorted only on the valid elements -
out = np.full(bins.shape, np.nan)
mask = np.in1d(bins,ids+1)
out.ravel()[mask] = np.take(values, np.searchsorted(ids+1, bins.ravel()[mask]))

Related

Finding mode of unique array combination in the rows of 2d numpy array

I have a 2d numpy array which I'm trying to return the mode array along axis = 0 (rows). However, I would like to return the most frequent unique row combination. And not the three modes for all three columns which is what scipy stats mode does. The desired output in the example below would be [9,9,9], because thats the most common unique row. Thanks
from scipy import stats
arr1 = np.array([[2,3,4],[2,1,5],[1,2,3],[2,4,4],[2,8,2],[2,3,1],[9,9,9],[9,9,9]])
stats.mode(arr1, axis = 0)
output:
ModeResult(mode=array([[2, 3, 4]]), count=array([[5, 2, 2]]))
you could use the numpy unique funtion and return counts.
unique_arr1, count = np.unique(arr1,axis=0, return_counts=True)
unique_arr1[np.argmax(count)]
output:
array([9, 9, 9])
np.unique return the unique array in sorted order, which means it is guranteed that last one is the maximum. you could simply do:
out = np.unique(arr1,axis=0)[-1]
however, I do not know for what purpose you want to use this but just to mention that you could have all counts just in case you want to verify or account for multiple rows with same counts as well.
Update
given additional information that this is for images (which could be big) and most importantly second dim could fit in a int (either each values is uin8 or 16) could fir in int32 or 64. (considering of values of each pixel in uint8):
pixel, count = np.unique(np.dot(arr, np.array([2**16,2**8,1])), return_counts=True)
pixel = pixel[np.argmax(count)]
b,g, r, = np.ndarray((3,), buffer=pixel, dtype=np.uint8)
This could result in a big speedup.

Compare two unequal size numpy arrays and fill the exclusion elements with nan

I need to do an element-by-element match of a 6x3 array with a 2x2 array. Return the bigger array with a True or False in the corresponding elements based on a match or no match. For the elements in the bigger array that cannot be compared e.g. column 3 and rows 3 to 6, I need to fill with NaN.
Here's my pseudo code:
first_arr_rows, first_arr_cols = 6, 3 # This is an example and will be dynamically initialized
sec_arr_rows, sec_arr_cols = 2, 2 # This is an example and will be dynamically initialized
if (sec_arr_cols <= first_arr_cols) and (sec_arr_rows <= first_arr_rows):
compared = arr1[:sec_arr_rows,:sec_arr_cols] == arr2[:sec_arr_rows,:sec_arr_cols]
# the above statement creates a 2x2 array
new_cols = np.zeros((first_arr_rows, first_arr_cols - sec_arr_cols))
new_rows = np.zeros((first_arr_rows - sec_arr_rows, compared.shape[1]))
compared = np.append(compared, new_rows, axis=0)
compared = np.append(compared, new_cols, axis=1)
compared[sec_arr_rows+1:,:] = np.nan
compared[:,sec_arr_cols:] = np.nan
Is there a simpler, more efficient way in Python to achieve this?
Here is my solution assuming the first array is always bigger than the second (see comments for general solution, e.g for the second array is bigger on some dimension)
import numpy as np
a = np.arange(18).reshape(6, 3) # 6x3 array
b = np.arange(4).reshape(2, 2) # 2x2 array
# create a resulting array of `nan` values
# in general case, desired shape is
# np.max([a.shape, b.shape], axis=0)
result = np.full(a.shape, np.nan)
# our selection have a shape of the smaller array
# in general case:
# tuple(map(slice, np.min([a.shape, b.shape], axis=0)))
selection = (slice(b.shape[0]), slice(b.shape[1]))
# compare values according the selection
result[selection] = a[selection] == b[selection]

How to have an array with no pair of elements closer by a distance

I want to remove elements from a numpy vector that are closer than a distance d. (I don't want any pair in the array or list that have a smaller distance between them than d but don't want to remove the pair completely otherwise.
for example if my array is:
array([[0. ],
[0.9486833],
[1.8973666],
[2.8460498],
[0.9486833]], dtype=float32)
All I need is to remove either the element with the index 1 or 4 not both of them.
I also need the indices of the elements from the original array that remain in the latent one.
Since the original array is in tensorflow 2.0, I will be happier if conversion to numpy is not needed like above. Because of speed also I prefer not to use another package and stay with numpy or scipy.
Thanks.
Here's a solution, using only a list. Note that this modifies the original list, so if you want to keep the original, copy.deepcopy it.
THRESHOLD = 0.1
def wrangle(l):
for i in range(len(l)):
for j in range(len(l)-1, i, -1):
if abs(l[i] - l[j]) < THRESHOLD:
l.pop(j)
using numpy:
import numpy as np
a = np.array([[0. ],
[0.9486833],
[1.8973666],
[2.8460498],
[0.9486833]])
threshold = 1.0
# The indices of the items smaller than a certain threshold, but larger than 0.
smaller_than = np.flatnonzero(np.logical_and(a < threshold, a > 0))
# Get the first index smaller than threshold
first_index = smaller_than[0]
# Recreate array without this index (bit cumbersome)
new_array = a[np.arange(len(a)) != first_index]
I'm pretty sure this is really easy to recreate in tensorflow, but I don't know how.
If your array is really only 1-d you can flatten it and do something like this:
a=tf.constant(np.array([[0. ],
[0.9486833],
[1.8973666],
[2.8460498],
[0.9486833]], dtype=np.float32))
d = 0.1
flat_a = tf.reshape(a,[-1]) # flatten
a1 = tf.expand_dims(flat_a, 1)
a2 = tf.expand_dims(flat_a, 0)
distance_map = tf.math.abs(a1-a2)
too_small = tf.cast(tf.math.less(dist_map, d), tf.int32)
# 1 at indices i,j if the distance between elements at i and j is less than d, 0 otherwise
upper_triangular_part = tf.linalg.band_part(too_small, 0, -1) - tf.linalg.band_part(too_small, 0,0)
remove = tf.reduce_sum(upper_triangular_part, axis=0)
remove = tf.cast(tf.math.greater(remove, 0), tf.float32)
# 1. at indices where the element should be removed, 0. otherwise
output = flat_a - remove * flat_a
You can access the indices through the remove tensor. If you need the extra dimension you can just use tf.expand_dims at the end of this.

Improving performance of a Python function that outputs the pixels that are different between two images

I'm working on a computer vision project and am looking to build a fast function that compares two images and outputs only the pixels where the differences between the pixels of the two images are sufficiently different. Other pixels get set to (0,0,0). In practice, I want the camera to detect objects and ignore the background.
My issue is the function doesn't run fast enough. What are some ways to speed things up?
def get_diff_image(fixed_image):
#get new image
new_image = current_image()
#get diff
diff = abs(fixed_image-new_image)
#creating a filter array
filter_array = np.empty(shape = (fixed_image.shape[0], fixed_image.shape[1]))
for idx, row in enumerate(diff):
for idx2, pixel in enumerate(row):
mag = np.linalg.norm(pixel)
if mag > 40:
filter_array[idx][idx2] = True
else:
filter_array[idx][idx2] = False
#applying filter
filter_image = np.copy(new_image)
filter_image[filter_array == False] = [0, 0, 0]
return filter_image
As others have mentioned, your biggest slow down in this code is iterating over every pixel in Python. Since Python is an interpreted language, these iterations take much longer than their equivalents in C/C++, which numpy uses under the hood.
Conveniently, you can specify an axis for numpy.linalg.norm, so you can get all the magnitudes in one numpy command. In this case, your pixels are on axis 2, so we'll take the norm on that axis, like this:
mags = np.linalg.norm(diff, axis=2)
Here, mags will have the same shape as filter_array, and each location will hold the magnitude of the corresponding pixel.
Using a boolean operator on a numpy array returns an array of bools, so:
filter_array = mags > 40
With the loops removed, the whole thing looks like this:
def get_diff_image(fixed_image):
#get new image
new_image = current_image()
#get diff
diff = abs(fixed_image-new_image)
#creating a filter array
mags = np.linalg.norm(diff, axis=2)
filter_array = mags > 40
#applying filter
filter_image = np.copy(new_image)
filter_image[filter_array == False] = [0, 0, 0]
return filter_image
But there is still more efficiency to be gained.
As noted by pete2fiddy, the magnitude of a vector doesn't depend on its direction. The absolute value operator only changes direction, not magnitude, so we just don't need it here. Sweet!
The biggest remaining performance gain is to avoid copying the image. If you need to preserve the original image, start by allocating zeros for the output array since zeroing memory is often hardware accelerated. Then, copy only the required pixels. If you don't need the original and only plan to use the filtered one, then modifying the image in-place will give much better performance.
Here's an updated function with those changes in place:
def get_diff_image(fixed_image):
#get new image
new_image = current_image()
# Compute difference magnitudes
mags = np.linalg.norm(fixed_image - new_image, axis=2)
# Preserve original image
filter_image = np.zeros_like(new_image)
filter_image[mag > 40] = new_image[mag > 40]
return filter_image
# Avoid copy entirely (overwrites original!)
# new_image[mag < 40] = [0, 0, 0]
# return new_image
The slow part is your loop.
for idx, row in enumerate(diff):
for idx2, pixel in enumerate(row):
mag = np.linalg.norm(pixel)
if mag > 40:
filter_array[idx][idx2] = True
else:
filter_array[idx][idx2] = False
Doing this in Python code is much slower than doing this in numpy's implicit loop over an array. I believe that this would apply to the norm call in which case you would have mag = np.linalg.norm(diff) which would create the array mag. You could the use numpy's greater function applied to the whole array to get filter_array.
The loop over the array will then be done in numpy's C code which is an order of magnitude faster (in general).
Nested loops in python run very slowly. However, this can often be mitigated through letting numpy which is largely written in C. Try giving this a go:
def get_image_difference_magnitudes(image1, image2):
'''order of subtraction does not matter, because the magnitude
of any vector is always a positive scalar'''
image_subtractions = image1-image2
'''checks if the image has channels or not, i.e.
if the two images are grayscaled or not. Calculating the magnitude
of change differs for both cases'''
if len(image1.shape) == 2:
return np.abs(image_subtractions)
'''returns the magnitude of change for each pixel. The "axis = 2"
part of np.linalg.norm specifies that, rather than the output that
does not specify the axis, it will return an image that replaces each
pixel, which contains a vector of the difference between the two images,
with the magnitude of the vector at that pixel'''
return np.linalg.norm(image_subtractions, axis = 2)
def difference_magnitude_threshold_images(image1, image2, threshold):
image_subtraction_magnitudes = get_image_difference_magnitudes(image1, image2)
'''creates a numpy array of "False" that is the same shape as the
width and height of the image. Slicing the shape to 2 makes it so
that the mask is only width x height, and not width x height x 3,
in the case of RGB'''
threshold_mask = np.zeros(image1.shape[:2], dtype = np.bool)
'''checks each element of image_subtraction_magnitudes, and for
all instances where the magnitude of difference at that pixel is
greater than threshold, set the mask = to True'''
threshold_mask[image_subtraction_magnitudes > threshold] = True
return threshold_mask
The above also has the benefit of being only a few lines long (were you to fit it into one function). The way you apply the filter looks fine to me -- I'm not sure if there is a faster way to do it. Also, I apologize for the formatting. I wasn't sure how to indent within a code block, so I left the function names outside of the code block.

numpy: unique list of colors in the image

I have an image img:
>>> img.shape
(200, 200, 3)
On pixel (100, 100) I have a nice color:
>>> img[100,100]
array([ 0.90980393, 0.27450982, 0.27450982], dtype=float32)
Now my question is: How many different colors are there in this image, and how do I enumerate them?
My first idea was numpy.unique(), but somehow I am using this wrong.
Your initial idea to use numpy.unique() actually can do the job perfectly with the best performance:
numpy.unique(img.reshape(-1, img.shape[2]), axis=0)
At first, we flatten rows and columns of matrix. Now the matrix has as much rows as there're pixels in the image. Columns are color components of each pixels.
Then we count unique rows of flattened matrix.
You could do this:
set( tuple(v) for m2d in img for v in m2d )
One straightforward way to do this is to leverage the de-duplication that occurs when casting a list of all pixels as a set:
unique_pixels = np.vstack({tuple(r) for r in img.reshape(-1,3)})
Another way that might be of practical use, depending on your reasons for extracting unique pixels, would be to use Numpy’s histogramdd function to bin image pixels to some pre-specified fidelity as follows (where it is assumed pixel values range from 0 to 1 for a given image channel):
n_bins = 10
bin_edges = np.linspace(0, 1, n_bins + 1)
bin_centres = (bin_edges[0:-1] + bin_edges[1::]) / 2.
hist, _ = np.histogramdd(img.reshape(-1, 3), bins=np.vstack(3 * [bin_edges]))
unique_pixels = np.column_stack(bin_centres[dim] for dim in np.where(hist))
If for any reason you will need to count the number of times each unique color appears, you can use this:
from collections import Counter
Counter([tuple(colors) for i in img for colors in i])
The question about unique colors (or more generally unique values along a given axis) has been also asked here (in particular, see this answer). If you're seeking for the fastest available option then "void view" would be your weapon of choice:
axis=2
np.unique(
img.view(np.dtype((np.void, img.dtype.itemsize*img.shape[axis])))
).view(img.dtype).reshape(-1, img.shape[axis])
For any questions related to what the script actually does, I refer the reader to the links above.

Categories

Resources