I want to compare two numpy arrays and create a third array - python

Like the title says, I want to compare two sitk arrays that have 1s and 0s as elements, and create a 3rd array that has 1s for where both arrays have 1 and 0s for any other cases. The arrays are the same size and are 3 dimensional, but is there a more efficient way to do this than iterating through them with nested for-loops?

import numpy as np
a = np.random.randint(low=0, high=2, size=(2,3,4), dtype=np.int)
print(a)
b = np.random.randint(low=0, high=2, size=(2,3,4), dtype=np.int)
print(b)
c = np.logical_and(a,b).astype(int)
print(c)
Is that what you're looking for?

arr_shape = (1,4,3)
a = np.random.randint(low=0,high=2, size=arr_shape)
print(a)
b = np.random.randint(low=0,high=2, size=arr_shape)
print(b)
# the new array. subtract a and b and get the absolute value.
# then invert to get the required array
d = (~abs(b - a).astype(bool)).astype(int)
print(d)
output:
[[[1 1 0]
[1 0 0]
[0 1 1]
[1 1 0]]]
[[[0 1 0]
[0 1 0]
[1 0 0]
[0 0 1]]]
array([[[0, 1, 1],
[0, 0, 1],
[0, 0, 0],
[0, 0, 0]]])

If you have SimpleITK Images, you can use the And function.
import SimpleITK as sitk
result = sitk.And(image1, image2)
This is a functional version of the AndImageFilter. You can read the docs for the class here:
https://simpleitk.org/doxygen/latest/html/classitk_1_1simple_1_1AndImageFilter.html

Related

All combinations of a numpy 2d array filled with 0s and 1s

Given K, I need to have all the possibile combinations of K x 2 numpy matrices so that in each matrix there are all 0s except for two 1s in different rows and columns.
Something like this for K = 5:
[[1,0],[0,1],[0,0],[0,0][0,0]]
[[1,0],[0,0],[0,1],[0,0][0,0]]
[[1,0],[0,0],[0,0],[0,1][0,0]]
[[1,0],[0,0],[0,0],[0,0][0,1]]
[[0,0],[1,0],[0,1],[0,0][0,0]]
[[0,0],[1,0],[0,0],[0,1][0,0]]
... and so on
So the resulting array should be a K x 2 x (K*(K-1)/2).
I want to avoid loops since it's not an efficient way when K is big enough (in my specific case K = 300)
I can't think of an elegant solution but here's a not-so-elegant pure numpy one:
import numpy as np
def combination_matrices(K):
# get combination indices
i, j = np.indices((K, K))
comb_indices = np.transpose((i < j).nonzero()) # (num_combs, 2) array where ones are
num_combs = comb_indices.shape[0] # K*(K-1)/2
# create a matrix of the desired shape, first axis enumerates combinations
matrices = np.zeros((num_combs, K, 2), dtype=int)
# broadcasting assignment of ones
comb_range, col_index = np.ogrid[:num_combs, :2]
matrices[comb_range, comb_indices, col_index] = 1
return matrices
This first uses the indices of a (K, K)-shaped array to find the index pairs for every combination (these are indices that encode the upper triangle of the array, excluding the diagonal). Then we use a bit tricky broadcasting assignment (heavy fancy indexing) to set each corresponding element of the pre-allocated output array to 1.
Note that I put the K*(K-1)/2-sized axis first, because this makes the most sense in numpy with C-contiguous memory layout. This way when you take the matrix for combination index 3, arr[3, ...] will be a contiguous chunk of memory of shape (K, 2) that's fast to work with in vectorised operations.
The output for K = 4:
[[[1 0]
[0 1]
[0 0]
[0 0]]
[[1 0]
[0 0]
[0 1]
[0 0]]
[[1 0]
[0 0]
[0 0]
[0 1]]
[[0 0]
[1 0]
[0 1]
[0 0]]
[[0 0]
[1 0]
[0 0]
[0 1]]
[[0 0]
[0 0]
[1 0]
[0 1]]]
This is an oddly specific question, but an interesting problem, I'd love to know what the context is?
You are looking for all permutations of a multiset , python's itertools doesn't currently support this. So simplest solution is to use the multiset tools of the sympy library.
The following code took about ~2.5 minutes to run on my machine, so is fairly fast for a single thread. You're looking at 179700 unique permutations for K=300.
(I took inspiration from https://stackoverflow.com/a/40289807/10739860)
from collections import Counter
from math import factorial, prod
import numpy as np
from sympy.utilities.iterables import multiset_permutations
from tqdm import tqdm
def No_multiset_permutations(multiset: list) -> int:
"""Calculates the No. possible permutations given a multiset.
See: https://en.wikipedia.org/wiki/Permutation#Permutations_of_multisets
:param multiset: List representing a multiset.
"""
value_counts = Counter(multiset).values()
denominator = prod([factorial(val) for val in value_counts])
return int(factorial(len(multiset)) / denominator)
def multiset_Kx2_permutations(K: int) -> np.ndarray:
"""This will generate all possible unique Kx2 permutations of an array
withsize K where two values are 1 and the rest are 0.
:param K: The size of the array.
"""
# Construct number multiset, e.g. K=5 gives [1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
numbers = [1, 1] + [0] * (K - 1) * 2
# Use sympy's multiset_permutations to get a multiset permutation generator
generator = multiset_permutations(numbers)
# Calculate the No. possible permutations
number_of_perms = No_multiset_permutations(numbers)
# Get all permutations, bonus progress bar is included :)
unique_perms = [next(generator) for _ in tqdm(range(number_of_perms))]
# Reshape each permutation to Kx2
unique_perms = np.array(unique_perms, dtype=np.int8)
return unique_perms.reshape(-1, K, 2)
if __name__ == "__main__":
solution = multiset_Kx2_permutations(300)
Another possibility (with rearranged axes for clearer output):
from itertools import combinations
import numpy as np
k = 4
x = list(combinations(range(k), 2))
out = np.zeros((n := len(x), k, 2), dtype=int)
out[np.c_[:n], x, [0, 1]] = 1
print(out)
It gives:
[[[1 0]
[0 1]
[0 0]
[0 0]]
[[1 0]
[0 0]
[0 1]
[0 0]]
[[1 0]
[0 0]
[0 0]
[0 1]]
[[0 0]
[1 0]
[0 1]
[0 0]]
[[0 0]
[1 0]
[0 0]
[0 1]]
[[0 0]
[0 0]
[1 0]
[0 1]]]

How to center the nonzero values within 2D numpy array?

I'd like to locate all the nonzero values within a 2D numpy array and move them so that the image is centered. I do not want to pad the array because I need to keep it the same shape. For example:
my_array = np.array([[1, 1, 0, 0], [0, 0, 2, 4], [0, 0, 0, 0], [0, 0, 0, 0]])
# center...
>>> [[0 0 0 0]
[0 1 1 0]
[0 2 4 0]
[0 0 0 0]]
But in reality the arrays I need to center are much larger (like 200x200, 403x403, etc, and they are all square). I think np.nonzero and np.roll might come in handy, but am not sure of the best way to use these for my large arrays.
The combination of nonzero and roll can be used for this purpose. For example, if k=0 in the loop shown below, then np.any will identify the rows that are not identically zero. The first and last such rows are noted, and the shift along the axis is computed so that after the shift, (first+last)/2 will move to the middle row of the array. Then the same is done for columns.
import numpy as np
my_array = np.array([[1, 1, 0, 0], [2, 4, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])
print(my_array) # before
for k in range(2):
nonempty = np.nonzero(np.any(my_array, axis=1-k))[0]
first, last = nonempty.min(), nonempty.max()
shift = (my_array.shape[k] - first - last)//2
my_array = np.roll(my_array, shift, axis=k)
print(my_array) # after
Before:
[[1 1 0 0]
[2 4 0 0]
[0 0 0 0]
[0 0 0 0]]
After:
[[0 0 0 0]
[0 1 1 0]
[0 2 4 0]
[0 0 0 0]]
Alternative: np.count_nonzeros can be used in place of np.any, which allows to potentially set some threshold for the number of nonzero pixels that are deemed "enough" to qualify a row as a part of the image.

How to create a numpy array from itertools.combinations without looping

Is there a way to get this result without a loop? I've made a couple attempts at fancy indexing with W[range(W.shape[0]),... but have been so far unsuccessful.
import itertools
import numpy as np
n = 4
ct = 2
one_index_tuples = list(itertools.combinations(range(n), r=ct))
W = np.zeros((len(one_index_tuples), n), dtype='int')
for row_index, col_index in enumerate(one_index_tuples):
W[row_index, col_index] = 1
print(W)
Result:
[[1 1 0 0]
[1 0 1 0]
[1 0 0 1]
[0 1 1 0]
[0 1 0 1]
[0 0 1 1]]
You can use fancy indexing (advanced indexing) as follows:
# reshape the row index to 2d since your column index is also 2d so that the row index and
# column index will broadcast properly
W[np.arange(len(one_index_tuples))[:, None], one_index_tuples] = 1
W
#array([[1, 1, 0, 0],
# [1, 0, 1, 0],
# [1, 0, 0, 1],
# [0, 1, 1, 0],
# [0, 1, 0, 1],
# [0, 0, 1, 1]])
Try this:
[[ 1 if i in x else 0 for i in range(n) ] for x in itertools.combinations( range(n), ct )]

Replace value in an array by its index in a list

In a given array I want to replace the values by the index of this value in an other array (which doesn't contain duplicates). Here is a simple example of I'm trying to do:
import numpy as np
from copy import deepcopy
a = np.array([[0, 1, 2], [2, 1, 3], [0, 1, 3]])
chg = np.array([3, 0, 2, 1])
b = deepcopy(a)
for new, old in enumerate(chg):
b[a == old] = new
print b
# [[1 3 2] [2 3 0] [1 3 0]]
But I need to do that on large arrays so having an explicit loop is not acceptable in terms of execution time.
I can't figure out how to do that using fancy numpy functions...
take is your friend.
a = np.array([[0, 1, 2], [2, 1, 3], [0, 1, 3]])
chg = np.array([3, 0, 2, 1])
inverse_chg=chg.take(chg)
print(inverse_chg.take(a))
gives :
[[1 3 2]
[2 3 0]
[1 3 0]]
or more directly with fancy indexing: chg[chg][a], but inverse_chg.take(a) is three times faster.
You can convert chg to a 3D array by adding two new axes at the end of it and then perform the matching comparison with a, which would bring in NumPy's broadcasting to give us a 3D mask. Next up, get the argmax on the mask along the first axis to simulate "b[a == old] = new". Finally, replace the ones that had no matches along that axis with the corresponding values in a. The implementation would look something like this -
mask = a == chg[:,None,None]
out = mask.argmax(0)
invalid_pos = ~mask.max(0)
out[invalid_pos] = a[invalid_pos]
This type of replacement operation can be tricky to do in full generality with NumPy, although you could use searchsorted:
>>> s = np.argsort(chg)
>>> s[np.searchsorted(chg, a.ravel(), sorter=s).reshape(a.shape)]
array([[1, 3, 2],
[2, 3, 0],
[1, 3, 0]])
(Note: searchsorted doesn't just replace exact matches, so be careful if you have values in a that aren't in chg...)
pandas has a variety of tools which can make these operations on NumPy arrays much easier and potentially a lot quicker / more memory efficient for larger arrays. For this specific problem, pd.match could be used:
>>> pd.match(a.ravel(), chg).reshape(a.shape)
array([[1, 3, 2],
[2, 3, 0],
[1, 3, 0]])
This function also allows you to specify what value should be filled if a value is missing from chg.
Check this out:
a = np.array([3,4,1,2,0])
b = np.array([[0,0],[0,1],[0,2],[0,3],[0,4]])
c = b[a]
print(c)
It gives me back:
[[0 3]
[0 4]
[0 1]
[0 2]
[0 0]]
If you're working with numpy arrays you could do this.

Expand numpy array of indices into a matix

I have a numpy array of N integers ranging from 0 to M inclusive. I wish to treat them as indexes into an NxM matrix that contains a 1 in every position indicated by the array and a 0 everywhere else. For example, if given N=4, M=2 I have the following array
[1, 0, 2, 1]
I want to get this matrix
[0 1 0]
[1 0 0]
[0 0 1]
[0 1 0]
i.e. the row 0 has a one in column 1, row 1 has a 1 in column 0, etc.
How do I make this transformation in numpy?
This requires multi-dimensional array indexing.
a = np.array([1, 0, 2, 1])
z = np.zeros(12, dtype=int).reshape(4,3)
z[np.arange(a.size), a] = 1

Categories