How to pad a specific dimension of a numpy array? - python

I'm trying to create a class that Right Pads a Numpy array to have the shape (257, 87). Currently the array has shape (257, 24), so I only need to pad the 2nd dim. I've tried a few iterations of the below class, but it always pads both dimensions.
class Padder:
def __init__(self, mode="constant"):
self.mode = mode
def right_pad(self, array):
num_missing_items = 87 - array.shape[1]
padded_array = np.pad(array,
(num_missing_items, 0),
mode=self.mode)
return padded_array
This results in shape (320, 87).
I also tried indexing the input array
class Padder:
def __init__(self, mode="constant"):
self.mode = mode
def right_pad(self, array):
num_missing_items = 87 - array.shape[1]
padded_array = np.pad(array[1],
(num_missing_items, 0),
mode=self.mode)
return padded_array
But this only returns the padded 2nd dim, nothing in the first dim, shape = (87,). So I tried to create a new array with the first dim as the original array's 1st dim, and 2nd dim as the padded 2nd dim.
class Padder:
def __init__(self, mode="constant"):
self.mode = mode
def right_pad(self, array):
num_missing_items = 87 - array.shape[1]
padded_array = np.array([array[0], np.pad(array[1],
(num_missing_items, 0),
mode=self.mode)])
return padded_array
But this returns an array of shape (2,)
How can I use padding to get my array to shape (257, 870)?

Have a look at the docs for the pad_width parameter for np.pad:
https://numpy.org/doc/stable/reference/generated/numpy.pad.html
You can pass it a sequence of (before, after) tuples. In your case, the (before, after) for the first dimension needs to be (0, 0), and you can choose yourself for the second. Here is an example:
import numpy as np
arr = np.arange(12).reshape(4, 3)
padded = np.pad(arr, ((0, 0), (2, 3)))
array([[ 0, 0, 0, 1, 2, 0, 0, 0],
[ 0, 0, 3, 4, 5, 0, 0, 0],
[ 0, 0, 6, 7, 8, 0, 0, 0],
[ 0, 0, 9, 10, 11, 0, 0, 0]])

Related

How to split multi-dimensional arrays based on the unique indices of another array?

I have two torch tensors a and b:
import torch
torch.manual_seed(0) # for reproducibility
a = torch.rand(size = (5, 10, 1))
b = torch.tensor([3, 3, 1, 5, 3, 1, 0, 2, 1, 2])
I want to split the 2nd dimension of a (which is dim = 1 in the Python language) based on the unique values in b.
What I have tried so far:
# find the unique values and unique indices of b
unique_values, unique_indices = torch.unique(b, return_inverse = True)
# split a in where dim = 1, based on unique indices
l = torch.tensor_split(a, unique_indices, dim = 1)
I was expecting l to be a list of n number of tensors where n is the number of unique values in b. I was also expecting the tensors to have the shape (5, number of elements corresponding to unique_values, 1).
However, I get the following:
print(l)
(tensor([[[0.8198],
[0.9971],
[0.6984]],
[[0.7262],
[0.7011],
[0.2038]],
[[0.1147],
[0.3168],
[0.6965]],
[[0.0340],
[0.9442],
[0.8802]],
[[0.6833],
[0.7529],
[0.8579]]]), tensor([], size=(5, 0, 1)), tensor([], size=(5, 0, 1)), tensor([[[0.9971],
[0.6984],
[0.5675]],
[[0.7011],
[0.2038],
[0.6511]],
[[0.3168],
[0.6965],
[0.9143]],
[[0.9442],
[0.8802],
[0.0012]],
[[0.7529],
[0.8579],
[0.6870]]]), tensor([], size=(5, 0, 1)), tensor([], size=(5, 0, 1)), tensor([], size=(5, 0, 1)), tensor([[[0.8198],
[0.9971]],
[[0.7262],
[0.7011]],
[[0.1147],
[0.3168]],
[[0.0340],
[0.9442]],
[[0.6833],
[0.7529]]]), tensor([], size=(5, 0, 1)), tensor([[[0.9971]],
[[0.7011]],
[[0.3168]],
[[0.9442]],
[[0.7529]]]), tensor([[[0.6984],
[0.5675],
[0.8352],
[0.2056],
[0.5932],
[0.1123],
[0.1535],
[0.2417]],
[[0.2038],
[0.6511],
[0.7745],
[0.4369],
[0.5191],
[0.6159],
[0.8102],
[0.9801]],
[[0.6965],
[0.9143],
[0.9351],
[0.9412],
[0.5995],
[0.0652],
[0.5460],
[0.1872]],
[[0.8802],
[0.0012],
[0.5936],
[0.4158],
[0.4177],
[0.2711],
[0.6923],
[0.2038]],
[[0.8579],
[0.6870],
[0.0051],
[0.1757],
[0.7497],
[0.6047],
[0.1100],
[0.2121]]]))
Why do I get empty tensors like tensor([], size=(5, 0, 1)) and how would I achieve what I want to achieve?
From your description of the desired result:
I was also expecting the tensors to have the shape (5, number of elements corresponding to unique_values, 1).
I believe you are looking for the count (or frequency) of unique values. If you want to keep using torch.unique, then you can provide the return_counts argument combined with a call to torch.cumsum.
Something like this should work:
>>> indices = torch.cumsum(counts, dim=0)
>>> splits = torch.tensor_split(a, indices[:-1], dim = 1)
Let's have a look:
>>> for x in splits:
... print(x.shape)
torch.Size([5, 1, 1])
torch.Size([5, 3, 1])
torch.Size([5, 2, 1])
torch.Size([5, 3, 1])
torch.Size([5, 1, 1])
Are you looking for the index_select method?
You have correclty obtained your unique values in unique_values.
Now what you need to do is:
l = a.index_select(1, unique_values)

How to select indices according to another tensor in pytorch

The task seems to be simple, but I cannot figure out how to do it.
So what I have are two tensors:
an indices tensor indices with shape (2, 5, 2), where the last dimensions corresponds to indices in x and y dimension
a "value tensor" value with shape (2, 5, 2, 16, 16), where I want the last two dimensions to be selected with x and y indices
To be more concrete, the indices are between 0 and 15 and I want to get an output:
out = value[:, :, :, x_indices, y_indices]
The shape of the output should therefore be of (2, 5, 2). Can anybody help me here? Thanks a lot!
Edit:
I tried the suggestion with gather, but unfortunately it does not seem to work (I changed the dimensions, but it doesn't matter):
First I generate a coordinate grid:
y_t = torch.linspace(-1., 1., 16, device='cpu').reshape(16, 1).repeat(1, 16).unsqueeze(-1)
x_t = torch.linspace(-1., 1., 16, device='cpu').reshape(1, 16).repeat(16, 1).unsqueeze(-1)
grid = torch.cat((y_t, x_t), dim=-1).permute(2, 0, 1).unsqueeze(0)
grid = grid.unsqueeze(1).repeat(1, 3, 1, 1, 1)
In the next step, I am creating some indices. In this case, I always take index 1:
indices = torch.ones([1, 3, 2], dtype=torch.int64)
Next, I am using your method:
indices = indices.unsqueeze(-1).unsqueeze(-1)
new_coords = torch.gather(grid, -1, indices).squeeze(-1).squeeze(-1)
Finally, I manually select index 1 for x and y coordinate:
new_coords_manual = grid[:, :, :, 1, 1]
This outputs the following new coordinates:
new_coords
tensor([[[-1.0000, -0.8667],
[-1.0000, -0.8667],
[-1.0000, -0.8667]]])
new_coords_manual
tensor([[[-0.8667, -0.8667],
[-0.8667, -0.8667],
[-0.8667, -0.8667]]])
As you can see, it only works for one dimension. Do you have an idea how to fix that?
What you could do is flatten the first three axes together and apply torch.gather:
>>> grid.flatten(start_dim=0, end_dim=2).shape
torch.Size([6, 16, 16])
>>> torch.gather(grid.flatten(0, 2), axis=1, indices)
tensor([[[-0.8667, -0.8667],
[-0.8667, -0.8667],
[-0.8667, -0.8667]]])
As explained on the documentation page, this will perform:
out[i][j][k] = input[i][index[i][j][k]][k] # if dim == 1
I figured it out, thanks again #Ivan for your help! :)
The problem was, that i unsqueezed on the last dimension, while I should have unsqueezed in the middle dimensions, so that the indices are at the end:
y_t = torch.linspace(-1., 1., 16, device='cpu').reshape(16, 1).repeat(1, 16).unsqueeze(-1)
x_t = torch.linspace(-1., 1., 16, device='cpu').reshape(1, 16).repeat(16, 1).unsqueeze(-1)
grid = torch.cat((y_t, x_t), dim=-1).permute(2, 0, 1).unsqueeze(0)
grid = grid.unsqueeze(1).repeat(2, 3, 1, 1, 1)
indices = torch.ones([2, 3, 2], dtype=torch.int64).unsqueeze(-2).unsqueeze(-2)
new_coords = torch.gather(grid, 3, indices).squeeze(-2).squeeze(-2)
new_coords_manual = grid[:, :, :, 1, 1]
Now new_coords equals new_coords_manual.

Zero pad an array to the next power-of-2 size

As mentioned in Zero pad numpy array, this function zero pads an array:
A = np.array([1,2,3,4,5])
B = np.pad(A, (0, 3), 'constant') # array([1, 2, 3, 4, 5, 0, 0, 0])
Question: Is there a helper function in numpy or scipy that automatically zero pads an array to the next power-of-2 size? (Useful for FFT purposes, etc.)
Of course I can do something like
def padpower2(A):
return np.pad(A, (0, int(2**np.ceil(np.log2(len(A)))) - len(A)), 'constant')
but it's alway tricky to remember/rewrite, so having a ready-to-use numpy function would be better.

How to create binary matrix given indices in tensorflow

Suppose I have a tf tensor with indices for two samples:
x = [[2,3,5], [5,7,5]]
I would like to create a tensor with a certain shape (samples, 10), where the indices of each sample in x are set to 1 and the rest to 0 like this:
output = [[0, 0, 1, 1, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 1, 0, 0]]
What is the best way to do this, without creating a lot of intermediary matrices?
The closest I got was using tf.scatter_nd, but I couldn't figure out how to transform x and the updates correctly, except manually adding additional information like this:
>>> tf.cast(tf.scatter_nd([[0,2], [0,3], [0,5], [1,5], [1,7], [1,5]], [1, 1, 1, 1, 1, 1] ,
[2, 10]) > 0, dtype="int64")
<tf.Tensor: id=1191, shape=(2, 10), dtype=int64, numpy=
array([[0, 0, 1, 1, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 1, 0, 0]])>
Also, this approach will aggregate duplicate indices at first, which makes an intermediary boolean matrix necessary. (This I could live with though, the main problem is getting from x to a matrix with shape (samples, 10) where non-existent indices are 0 for each sample.)
Thanks for any help! :)
I found a solution (tensorflow 2.2.0):
class BinarizeSequence(tf.keras.layers.Layer):
"""
Transforms an integer sequence into a binary representation
with shape (samples, vocab_size).
Example:
In: [[2,3,5], [5,7,5]]
Out: [[0, 0, 1, 1, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 1, 0, 0]]
By default the output is returned as SparseTensor.
Use dense_output=True if you need a dense representation.
"""
def __init__(self, vocab_size, dense_output=False, **kwargs):
super(BinarizeSequence, self).__init__(**kwargs)
self.vocab_size = vocab_size
self.dense_output = dense_output
def get_config(self):
config = super().get_config().copy()
config.update(
{"vocab_size": self.vocab_size, "dense_output": self.dense_output}
)
return config
def call(self, x, mask=None):
# create indices for binarized representation
x = tf.cast(x, dtype=tf.int32)
x_1d = tf.reshape(x, [-1])
sample_dim = tf.repeat(
tf.range(tf.shape(x)[0], dtype=tf.int32), tf.shape(x)[1]
)
indices = tf.transpose(tf.stack([sample_dim, x_1d]))
# only keep unique indices
# (see https://stackoverflow.com/a/42245425/979377)
indices64 = tf.bitcast(indices, type=tf.int64)
unique64, idx = tf.unique(indices64)
unique_indices = tf.bitcast(unique64, type=tf.int32)
# build binarized representation
updates = tf.ones(tf.shape(unique_indices)[0])
output_shape = [tf.shape(x)[0], self.vocab_size]
if self.dense_output:
output = tf.scatter_nd(unique_indices, updates, output_shape)
else:
output = tf.sparse.SparseTensor(
tf.cast(unique_indices, tf.int64), updates, output_shape
)
return output

Crop empty arrays (padding) from a volume

What I want to do is crop a volume to remove all irrelevant data. For example, say I have a 100x100x100 volume filled with zeros, except for a 50x50x50 volume within that is filled with ones.
How do I obtain the cropped 50x50x50 volume from the original ?
Here's the naive method I came up with.
import numpy as np
import tensorflow as tf
test=np.zeros((100,100,100)) # create an empty 100x100x100 volume
rand=np.random.rand(66,25,34) # create a 66x25x34 filled volume
test[10:76, 20:45, 30:64] = rand # partially fill the empty volume
# initialize the cropping coordinates
minx=miny=minz=0
maxx=maxy=maxz=0
maxx,maxy,maxz=np.subtract(test.shape,1)
# compute the optimal cropping coordinates
dimensions=test.shape
while(tf.reduce_max(test[minx,:,:]) == 0): # check for empty slices along the x axis
minx+=1
while(tf.reduce_max(test[:,miny,:]) == 0): # check for empty slices along the y axis
miny+=1
while(tf.reduce_max(test[:,:,minz]) == 0): # check for empty slices along the z axis
minz+=1
while(tf.reduce_max(test[maxx,:,:]) == 0):
maxx-=1
while(tf.reduce_max(test[:,maxy,:]) == 0):
maxy-=1
while(tf.reduce_max(test[:,:,maxz]) == 0):
maxz-=1
maxx,maxy,maxz=np.add((maxx,maxy,maxz),1)
crop = test[minx:maxx,miny:maxy,minz:maxz]
print(minx,miny,minz,maxx,maxy,maxz)
print(rand.shape)
print(crop.shape)
This prints:
10 20 30 76 45 64
(66, 25, 34)
(66, 25, 34)
, which is correct. However, it takes too long and is probably suboptimal. I'm looking for better ways to achieve the same thing.
NB:
The subvolume wouldn't necessarily be a cuboid, it could be any shape.
I want to keep gaps within the subvolume, only remove what's "outside" the shape to be cropped.
(Edit)
Oops, I hadn't seen the comment about keeping the so-called "gaps" between elements! This should be the one, finally.
def get_nonzero_sub(arr):
arr_slices = tuple(np.s_[curr_arr.min():curr_arr.max() + 1] for curr_arr in arr.nonzero())
return arr[arr_slices]
While you wait for a sensible response (I would guess this is a builtin function in an image processing library somewhere), here's a way
y, x = np.where(np.any(test, 0))
z, _ = np.where(np.any(test, 1))
test[min(z):max(z)+1, min(y):max(y)+1, min(x):max(x)+1]
I think leaving tf out of this should up your performance.
Explanation (based on 2D array)
test = np.array([
[0, 0, 0, 0, 0, ],
[0, 0, 1, 2, 0, ],
[0, 0, 3, 0, 0, ],
[0, 0, 0, 0, 0, ],
[0, 0, 0, 0, 0, ],
])
We want to crop it to get
[[1, 2]
[3, 0]]
np.any(..., 0) this will 'iterate' over axis 0 and return True if any of the elements in the slice are truthy. I show the result of this in the comments here:
np.array([
[0, 0, 0, 0, 0, ], # False
[0, 0, 1, 2, 0, ], # True
[0, 0, 3, 0, 0, ], # True
[0, 0, 0, 0, 0, ], # False
[0, 0, 0, 0, 0, ], # False
])
i.e. it returns np.array([False, True, True, False, False])
np.any(..., 1) does the same as step 2 but over axis 1 instead of axis zero i.e.
np.array([
[0, 0, 0, 0, 0, ],
[0, 0, 1, 2, 0, ],
[0, 0, 3, 0, 0, ],
[0, 0, 0, 0, 0, ],
[0, 0, 0, 0, 0, ],
# False False True True False
])
Note that in the case of a 3D array, these steps return 2D arrays
(x,) = np.where(...) this returns the index values of the truthy values in an array. So np.where([False, True, True, False, False]) returns (array([1, 2]),). Note that this is a tuple so in the 2D case we would need to call (x,) = ... so x is just the array array([1, 2]). The syntax is nicer in the 2D case as we can use tuple-unpacking i.e x, y = ...
Note that in the 3D case, np.where can give us the value for 2 axes at a time. I chose to do x-y in one go and then z-? in the second go. The ? is either x or y, I can't be bothered to work out which and since we don't need it I throw it away in a variable named _ which by convention is a reasonable place to store junk output you don't actually want. Note I need to do z, _ = as I want the tuple-unpacking and not just z = otherwise z become the tuple with both arrays.
Well, this step is pretty much the same as what you did at the end of your answer so I assume you understand it. Simple slicing in each dimension from the first element with a value in that dimension to the last. You need the + 1 because slicing in python are not inclusive of the index after the :.
Hopefully that's clear?

Categories