How to calculate all 24 rotations of 3d array?

后端 未结 9 738
清酒与你
清酒与你 2021-02-12 10:45

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

相关标签:
9条回答
  • 2021-02-12 11:18

    I encountered a similar problem when trying to randomly rotate a 3D one hot encoded numpy array, just before feeding into my neural network. I will demonstrate here for a 3D array, but this also works with a 4th dimension (when using OHE).

    >>> m = np.reshape(np.arange(8),(2,2,2))
    >>> m
    array([[[0, 1],
            [2, 3]],
    
           [[4, 5],
            [6, 7]]])
    

    Next we rotate the array 3 times, each time in a different direction. Repeat 24,000 times to see distribution (expecting 1000 counts for each unique rotation):

    >>> rot_list = []
    >>> for _ in range(24000):
            a = np.rot90(m,np.random.randint(0,4),axes=(0,np.random.randint(1,3)))
            b = np.rot90(a,np.random.randint(0,4),axes=(np.random.randint(1,3),0))
            c = np.rot90(b,np.random.randint(0,4),axes=(1,2)) # or axes=(2,1)
            rot_list.append(c)
    >>> unique_rotation_matrices, counts = np.unique(np.asarray(rot_list),axis=0, return_counts=True)
    >>> len(unique_rotation_matrices)
    24
    

    So we see we get all the 24 possible rotations. Let's look at their distribution:

    >>> counts
    [1062  946  917  982 1096  978 1153  936  939  907 1183  932  958  932 1122
      926 1115  954  933  932 1135  924 1138  900]
    

    Distribution looks quite even, but rerunning this a number of times reveals it is slightly biased.

    0 讨论(0)
  • 2021-02-12 11:24

    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.

    Generic idea to solve for all rotations

    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.


    Solve for 24 rotations

    Now, 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
    
    0 讨论(0)
  • 2021-02-12 11:24

    There are 24 rotation matrices at the bottom of this page: http://www.euclideanspace.com/maths/algebra/matrix/transforms/examples/index.htm .

    If a tetris piece were represented by an array of coordinate triples, to apply a rotation to the piece you would just matrix multiply the matrix by each of the triples in the array. You would do that 24 times to get the 24 different pieces.

    For example, if your array of boxes is (1,2,5), (1,2,4), (1,3,4)

    And the rotation you are considering at the moment is for example

        1 0  0
    M = 0 0 -1
        0 1  0
    

    Then the rotated figure has representation

    (1,2,5).M, (1,2,4).M, (1,3,4).M
    

    where the . represents matrix multiplication. Do that for each M in the list and you've got all 24 rotations. (You can either pre- or post- multiply by the matrices, it doesn't matter if you want the whole set of rotations in no particular order.)

    So that is very straightforward.

    Now, to get the data out of your data structure and into the array structure I have used above, you would have to do something like (sorry I don't know Python)

    for i = 0 to 2
      for j = 0 to 2 
        for k = 0 to 2
          if polycube(i,j,k)==1 then push(i-1,j-1,k-1) onto end of newArray
    

    or something like that. Finally, to go from the newArray representation to the polycube you would do something like

      polycube = all zeros
        foreach point (i,j,k) in newArray
          polycube(i+1,j+1,k+1) = 1
    

    For a 2x2x2 polycube, you would do

    for i = 0 to 1
      for j = 0 to 1 
        for k = 0 to 1
          if polycube(i,j,k)==1 then push(i-0.5,j-0.5,k-0.5) onto end of newArray
    

    and the corresponding inverse function.

    0 讨论(0)
提交回复
热议问题