Opposite of binary_dilation - python

Is there a function that does the opposite of binary_dilation? I'm looking to remove 'islands' from an array of 0's and 1's. That is, if a value of 1 in a 2D array doesn't have at least 1 adjacent neighbor that is also 1, its value gets set to 0 (rather than have its neighbor's values set equal to 1 as in binary_dilation). So for example:
test = np.zeros((5,5))
test[1,1] = test[1,2] = test[3,3] = test[4,3] = test[0,3] = test[3,1] = 1
test
array([[0., 0., 0., 1., 0.],
[0., 1., 1., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 1., 0., 1., 0.],
[0., 0., 0., 1., 0.]])
And the function I'm seeking would return:
array([[0., 0., 0., 0., 0.],
[0., 1., 1., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 1., 0.],
[0., 0., 0., 1., 0.]])
Note the values changed in locations [0,3] and [3,1] from 1 to 0 because they have no adjacent neighbors with a value equal 1 (diagonal doesn't count as a neighbor).

You can create a mask with the cells to check and do a 2d convolution with test to identify the cells with 1s adjacent to them. The logical and of the convolution and test should produce the desired output.
First define your mask. Since you are only looking for up/down and left/right adjacency, you want the following:
mask = np.ones((3, 3))
mask[1,1] = mask[0, 0] = mask[0, 2] = mask[2, 0] = mask[2, 2] = 0
print(mask)
#array([[0., 1., 0.],
# [1., 0., 1.],
# [0., 1., 0.]])
If you wanted to include diagonal elements, you'd simply update mask to include 1s in the corners.
Now apply a 2d convolution of test with mask. This will multiply and add the values from the two matrices. With this mask, this will have the effect of returning the sum of all adjacent values for each cell.
from scipy.signal import convolve2d
print(convolve2d(test, mask, mode='same'))
#array([[0., 1., 2., 0., 1.],
# [1., 1., 1., 2., 0.],
# [0., 2., 1., 1., 0.],
# [1., 0., 2., 1., 1.],
# [0., 1., 1., 1., 1.]])
You have to specify mode='same' so the result is the same size as the first input (test). Notice that the two cells that you wanted to remove from test are 0 in the convolution output.
Finally do a element wise and operation with this output and test to find the desired cells:
res = np.logical_and(convolve2d(test, mask, mode='same'), test).astype(int)
print(res)
#array([[0, 0, 0, 0, 0],
# [0, 1, 1, 0, 0],
# [0, 0, 0, 0, 0],
# [0, 0, 0, 1, 0],
# [0, 0, 0, 1, 0]])
Update
For the last step, you could also just clip the values in the convolution between 0 and 1 and do an element wise multiplication.
res = convolve2d(test, mask, mode='same').clip(0, 1)*test
#array([[0., 0., 0., 0., 0.],
# [0., 1., 1., 0., 0.],
# [0., 0., 0., 0., 0.],
# [0., 0., 0., 1., 0.],
# [0., 0., 0., 1., 0.]])

Related

Permutation sign for batched vectors - Python

I would like to find the permutation parity sign for a given batch of vectors (in Python /Jax).
n = jnp.array([[[0., 0., 1., 1.],
[0., 0., 1., 1.],
[1., 1., 0., 0.],
[1., 1., 0., 0.]],
[[1., 0., 1., 0.],
[0., 1., 0., 1.],
[1., 0., 1., 0.],
[0., 1., 0., 1.]],
[[0., 1., 1., 0.],
[1., 0., 0., 1.],
[1., 0., 0., 1.],
[0., 1., 1., 0.]]])
sorted_index = jax.vmap(sorted_idx)(n)
sorted_perms = jax.vmap(jax.vmap(sorted_perm, in_axes=(0, 0)), in_axes=(0,0))(n, sorted_index)
parities = jax.vmap(parities)(sorted_index)
I expect the following solution:
sorted_elements= [[[0., 0., 1., 1.],
[0., 0., 1., 1.],
[0., 0., 1., 1.],
[0., 0., 1., 1.]],
[[0., 0., 1., 1.],
[0., 0., 1., 1.],
[0., 0., 1., 1.],
[0., 0., 1., 1.]],
[[0., 0., 1., 1.],
[0., 0., 1., 1.],
[0., 0., 1., 1.],
[0., 0., 1., 1.]]]
parities = [[1, 1, 1, 1],
[-1, -1, -1, -1],
[1, 1, 1, 1]]
I tried the following:
# sort the array and return the arg_sort indices
def sorted_idx(permutations):
sort_idx = jnp.argsort(permutations)
return sort_idx
# sort the permutations (vectors) given the sorted_indices
def sorted_perm(permutations, sort_idx):
perm = permutations[sort_idx]
return perm
# Calculate the permutation cycle, from which we compute the permutation parity
#jax.vmap
def parities(sort_idx):
length = len(sort_idx)
elements_seen = jnp.zeros(length)
cycles = 0
for index in range(length):
if elements_seen[index] == True:
continue
cycles += 1
current = index
if elements_seen[current] == False:
elements_seen.at[current].set(True)
current = sort_idx[current]
is_even = (length - cycles) % 2 == 0
return +1 if is_even else -1
But I get the following: parities= [[1 1 1 1], [1 1 1 1], [1 1 1 1]]
I get for each permutation vector a parity factor of 1, which is wrong....
The reason your routine doesn't work is because you're attempting to vmap over Python control flow, and this must be done very carefully (See JAX Sharp Bits: Control Flow). I suspect it would be a bit complicated to try to construct your iterative parity approach in terms of jax.lax control flow operators, but there might be another way.
The parity of a permutation is related to the determinant of its cycle matrix, and the jacobian of a sort happens to be equivalent to that cycle matrix, so you could (ab)use JAX's automatic differentiation of the sort operator to compute the parities very concisely:
def compute_parity(p):
return jnp.linalg.det(jax.jacobian(jnp.sort)(p.astype(float))).astype(int)
sorted_index = jnp.argsort(n, axis=-1)
parities = jax.vmap(jax.vmap(compute_parity))(sorted_index)
print(parities)
# [[ 1 1 1 1]
# [-1 -1 -1 -1]
# [ 1 1 1 1]]
This does end up being O[N^3] where N is the length of the permutations, but due to the nature of XLA computations, particularly on accelerators like GPU, the vectorized approach will likely be more efficient than an iterative approach for reasonably-sized N.
Also note that there's no reason to compute the sorted_index with this implementation; you could call compute_parity directly on your array n instead.

In a pytorch tensor, return an array of indices of the rows of specific value

Given the below tensor that has vectors of all zeros and vectors with ones and zeros:
tensor([[0., 0., 0., 0.],
[0., 1., 1., 0.],
[0., 0., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 0.],
[0., 0., 1., 0.],
[1., 0., 0., 1.],
[0., 0., 0., 0.],...])
How can I have an array of indices of the vectors with ones and zeros so the output is like this:
indices = tensor([ 1, 3, 5, 6,...])
Update
A way to do it is:
indices = torch.unique(torch.nonzero(y>0,as_tuple=True)[0])
But I'm not sure if there's a better way to do it.
An alternative way is to use torch.Tensor.any coupled with torch.Tensor.nonzero:
>>> x.any(1).nonzero()[:,0]
tensor([1, 3, 5, 6])
Otherwise, since the tensor contains only positive value, you can sum the columns and mask:
>>> x.sum(1).nonzero()[:,0]
tensor([1, 3, 5, 6])

Numpy concatenate behaviour. How to concatenate example correctly

I have following multi-dimensional array:
windows = array([[[[[[0., 0.],
[1., 0.]],
[[0., 0.],
[1., 0.]],
[[0., 0.],
[1., 0.]]],
[[[0., 1.],
[0., 0.]],
[[0., 1.],
[0., 0.]],
[[1., 0.],
[0., 0.]]],
[[[1., 0.],
[0., 0.]],
[[0., 1.],
[0., 0.]],
[[0., 1.],
[0., 0.]]]]]])
print(windows.shape)
(1, 1, 3, 3, 2, 2) # (n, d, a, b, c, c) w = a * (c*c), h = b * (c*c)
I want to get next resulting array:
mask = array([[
[[0., 0., 0., 0., 0., 0.],
[1., 0., 1., 0., 1., 0.],
[0., 1., 0., 1., 1., 0.],
[0., 0., 0., 0., 0., 0.],
[1., 0., 0., 1., 0., 1.],
[0., 0., 0., 0., 0., 0.]]
]], dtype=np.float32)
print(mask.shape)
(1, 1, 6, 6) # (n, d, w, h)
Basically, I want to squeeze last 4 dimensions into 2-d matrix so the final shape become (n, d, w, h), in this case (1, 1, 6, 6).
I tried np.concatenate(windows, axis = 2), but it didnt concatenate along 2nd dimension and reduced for some reason first(although I set axis = 2) 'n' dimension.
Additional information:
windows is a result of following code snippet
windows = np.lib.stride_tricks.sliding_window_view(arr, (c, c), axis (-2,-1), writeable = True) # arr.shape == mask.shape
windows = windows[:, :, ::c, ::c] # these are non-overlapping windows of arr with size (c,c)
windows = ... # some modifications of windows
Now I want to build from these windows array with shape of arr.shape, this array called mask in example above. Simple reshape doesn't work because it returns elements in wrong order.
IIUC, you want to merge dimensions 2+4 and 3+5, an easy way would be to swapaxes 4 and 5 (or -3 and -2), and reshape to (1,1,6,6):
windows.swapaxes(-2,-3).reshape(1,1,6,6)
output:
array([[[[0., 0., 0., 0., 0., 0.],
[1., 0., 1., 0., 1., 0.],
[0., 1., 0., 1., 1., 0.],
[0., 0., 0., 0., 0., 0.],
[1., 0., 0., 1., 0., 1.],
[0., 0., 0., 0., 0., 0.]]]])

How to interpret numpy advanced indexing solution

I have a piece of numpy code that I know works. I know this because I have tested it in my generic case successfully. However, I arrived at the solution after two hours of back and forth referencing the docs and trial and error. I can't grasp how I would know to do this intuitively.
The setup:
a = np.zeros((5,5,3))
The goal: Set to 1 indices 0,1 of axis 1, 0,1 of axis 2, all of axis 3 and indices 3,4 of axis 1, 3,4 of axis 2, all of axis 3
Clearer goal: Set block 1 and 2's first two rows to 1 and block 3 and 4's last two rows to 1
The result:
ax1 =np.array([np.array([0,1]),np.array([3,4])])
ax1 =np.array([x[:,np.newaxis] for x in ax1])
ax2 = np.array([[[0,1]],[[3,4]]])
a[ax1,ax2,:] = 1
a
Output:
array([[[1., 1., 1.],
[1., 1., 1.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]],
[[1., 1., 1.],
[1., 1., 1.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]],
[[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]],
[[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[1., 1., 1.],
[1., 1., 1.]],
[[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[1., 1., 1.],
[1., 1., 1.]]])
I'm inclined to believe I should be able to look at the shape of the matrix in question, the shape of the indices, and the index operation to intuitively know the output. However, I can't put the story together in my head. Like, what's the final shape of the subspace it is altering? How would you explain how this works?
The shapes:
input: (5, 5, 3)
ind1: (2, 2, 1)
ind2: (2, 1, 2)
final_op: input[ind1, ind2, :]
With shapes
ind1: (2, 2, 1)
ind2: (2, 1, 2)
they broadcast together to select a (2,2,2) space
In [4]: ax1
Out[4]:
array([[[0],
[1]],
[[3],
[4]]])
In [5]: ax2
Out[5]:
array([[[0, 1]],
[[3, 4]]])
So for the 1st dimension (blocks) it is selecting blocks 0,1,3,and 4. In the second dimension it is also selecting these rows.
Together that's the first 2 rows of the first 2 blocks, and the last 2 rows of the last 2 blocks. That's where the 1s appear in your result.
A simpler way of creating the index arrays:
In [7]: np.array([[0,1],[3,4]])[:,:,None] # (2,2) expanded to (2,2,1)
In [8]: np.array([[0,1],[3,4]])[:,None,:] # expand to (2,1,2)
This is how broadcasting expands them:
In [10]: np.broadcast_arrays(ax1,ax2)
Out[10]:
[array([[[0, 0], # block indices
[1, 1]],
[[3, 3],
[4, 4]]]),
array([[[0, 1], # row indices
[0, 1]],
[[3, 4],
[3, 4]]])]
This may make the pattern clearer:
In [15]: a[ax1,ax2,:] = np.arange(1,5).reshape(2,2,1)
In [16]: a[:,:,0]
Out[16]:
array([[1., 2., 0., 0., 0.],
[3., 4., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 1., 2.],
[0., 0., 0., 3., 4.]])

How to fill numpy array of zeros with ones given indices/coordinates

Given a numpy array of zeros, say
arr = np.zeros((5, 5))
and an array of indices that represent vertices of a polygon, say
verts = np.array([[0, 2], [2, 0], [2, 4]])
1) What is the elegant way of doing
for v in verts:
arr[v[0], v[1]] = 1
such that the resulting array is
In [108]: arr
Out[108]:
array([[ 0., 0., 1., 0., 0.],
[ 0., 0., 0., 0., 0.],
[ 1., 0., 0., 0., 1.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.]])
2) How can I fill the array with ones such that the output array is
In [158]: arr
Out[158]:
array([[ 0., 0., 1., 0., 0.],
[ 0., 1., 1., 1., 0.],
[ 1., 1., 1., 1., 1.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.]])
To answer the first part of your question: arr[tuple(verts.T)] = 1
verts.T transposes your indices to a (2, n) array, where the two rows correspond to the row and column dimensions of arr. These are then unpacked into a tuple of (row_indices, col_indices), which we then use to index into arr.
We could write this a bit more verbosely as:
row_indices = verts[:, 0]
col_indices = verts[:, 1]
arr[row_indices, col_indices] = 1
For the second part, one method that will work for arbitrary polygons would be to use matplotlib.Path.contains_points, as described here:
from matplotlib.path import Path
points = np.indices(arr.shape).reshape(2, -1).T
path = Path(verts)
mask = path.contains_points(points, radius=1e-9)
mask = mask.reshape(arr.shape).astype(arr.dtype)
print(repr(mask))
# array([[ 0., 0., 1., 0., 0.],
# [ 0., 1., 1., 1., 0.],
# [ 1., 1., 1., 1., 1.],
# [ 0., 0., 0., 0., 0.],
# [ 0., 0., 0., 0., 0.]])

Categories

Resources