I have a 3d numpy array describing a polycube (imagine a 3d tetris piece). How can I calculate all 24 rotations?
Numpy\'s array manipulation routines includes a rot90 me
We would start off with the intention of getting all 48
combinations so that we get the general idea about solving it for a n-dim array. Later on we would filter out the unwanted 24
ones.
The idea to solve for a generic case would be to basically do two things - flip along every axis and permute axes with all combinations for the given number of axes.
Flip : To flip, we would use the stepsize parameter for slicing, i.e. array[::stepsize]. So, to flip, it would be : [::-1]
and without flipping, simply : [::1]
. That stepsize
could be assigned as a variable varying between 1
and -1
for the two combinations simpl. For a ndarray, simply extend this to all axes.
Permute axes : To achieve this, we can use np.transpose and specify the required permuted order as the axes parameter with it. We will generate all possible orders with itertools.permutations
.
That's all there is! Let's implement it with a
as the input 3D
array -
import itertools
def rotations48(a):
# Get all combinations of axes that are permutable
n = a.ndim
axcomb = np.array(list(itertools.permutations(range(n), n)))
# Initialize output array
out = np.zeros((6,2,2,2,) + a.shape,dtype=a.dtype)
# Run loop through all axes for flipping and permuting each axis
for i,ax in enumerate(axcomb):
for j,fx in enumerate([1,-1]):
for k,fy in enumerate([1,-1]):
for l,fz in enumerate([1,-1]):
out[i,j,k,l] = np.transpose(a[::fx,::fy,::fz],ax)
return out
We could simplify for the flipping nested loops with one loop -
def rotations48(a):
n = a.ndim
axcomb = list(itertools.permutations(range(n), n)) # all axes combinations
pcomb = list(itertools.product([1,-1], repeat=n)) # all permuted orders
out = np.zeros((6,8,) + a.shape,dtype=a.dtype) # Initialize output array
for i,ax in enumerate(axcomb): #loop through all axes for permuting
for j,(fx,fy,fz) in enumerate(pcomb): # all flipping combinations
out[i,j] = np.transpose(a[::fx,::fy,::fz],ax)
return out
So, this gets us all the 48
combinations.
Extend to more dimensions : If we want to extend this to a 4D
array, simply edit the initialization part to extend by 2
and slice along one more axis.
24
rotationsNow, as OP is claiming to have a working solution to get the desired 24
combinations, we need to filter out
from our proposed solution. I couldn't find a generic pattern for the filtering, but got the indices required for indexing -
idx = np.array([ 0, 3, 5, 6, 9, 10, 12, 15, 17, 18, 20, 23, 24, \
27, 29, 30, 32, 35, 37, 38, 41, 42, 44, 47])
If you care about the order to have the output same output as with rotations24, we would have -
idx = np.array([ 0, 10, 3, 9, 5, 15, 6, 12, 41, 27, 42, 24, 44, \
30, 47, 29, 18, 35, 17, 32, 20, 37, 23, 38])
Hence, get the required 24
ones with indexing
-
final_out = out.reshape(48,-1)[idx]
This works for 3D
arrays with any generic lengths.
Sample run for verification
# From https://stackoverflow.com/a/33190472/ @Colonel Panic
def rotations24_array(a):
out0 = np.zeros((6,2,2,2,) + a.shape,dtype=a.dtype)
p = [list(i) for i in rotations24(a)]
out0 = np.zeros((6,4,m,m,m),dtype=a.dtype)
for i in range(6):
for j in range(4):
out0[i,j] = p[i][j]
return out0
Verify -
In [486]: # Setup
...: np.random.seed(0)
...: m = 3
...: a = np.random.randint(11,99,(m,m,m))
...:
...: # Verify results
...: idx = np.array([ 0, 10, 3, 9, 5, 15, 6, 12, 41, 27, 42, 24, 44, \
...: 30, 47, 29, 18, 35, 17, 32, 20, 37, 23, 38])
...: out1 = rotations24_array(a).reshape(-1,m**3)
...: out2 = rotations48(a).reshape(48,-1)[idx]
...: print np.allclose(out1, out2)
True