[python] 3-dimensional array in numpy

New at Python and Numpy, trying to create 3-dimensional arrays. My problem is that the order of the dimensions are off compared to Matlab. In fact the order doesn't make sense at all.

Creating a matrix:

x = np.zeros((2,3,4))

In my world this should result in 2 rows, 3 columns and 4 depth dimensions and it should be presented as:

[0 0 0      [0 0 0      [0 0 0      [0 0 0
 0 0 0]      0 0 0]      0 0 0]      0 0 0] 

Seperating on each depth dimensions. Instead it is presented as

[0 0 0 0      [0 0 0 0
 0 0 0 0       0 0 0 0
 0 0 0 0]      0 0 0 0]

That is, 3 rows, 4 column and 2 depth dimensions. That is, the first dimension is the "depth". To further add to this problem, importing an image with OpenCV the color dimension is the last dimension, that is, I see the color information as the depth dimension. This complicates things greatly if all I want to do is try something on a known smaller 3-dimensional array.

Have I misunderstood something? If not, why the heck is numpy using such a unintuitive way of working with 3D-dimensional arrays?

This question is related to python numpy

The answer is


As much as people like to say "order doesn't matter its just convention" this breaks down when entering cross domain interfaces, IE transfer from C ordering to Fortran ordering or some other ordering scheme. There, precisely how your data is layed out and how shape is represented in numpy is very important.

By default, numpy uses C ordering, which means contiguous elements in memory are the elements stored in rows. You can also do FORTRAN ordering ("F"), this instead orders elements based on columns, indexing contiguous elements.

Numpy's shape further has its own order in which it displays the shape. In numpy, shape is largest stride first, ie, in a 3d vector, it would be the least contiguous dimension, Z, or pages, 3rd dim etc... So when executing:

np.zeros((2,3,4)).shape

you will get

(2,3,4)

which is actually (frames, rows, columns). doing np.zeros((2,2,3,4)).shape instead would mean (metaframs, frames, rows, columns). This makes more sense when you think of creating multidimensional arrays in C like langauges. For C++, creating a non contiguously defined 4D array results in an array [ of arrays [ of arrays [ of elements ]]]. This forces you to de reference the first array that holds all the other arrays (4th dimension) then the same all the way down (3rd, 2nd, 1st) resulting in syntax like:

double element = array4d[w][z][y][x];

In fortran, this indexed ordering is reversed (x is instead first array4d[x][y][z][w]), most contiguous to least contiguous and in matlab, it gets all weird.

Matlab tried to preserve both mathematical default ordering (row, column) but also use column major internally for libraries, and not follow C convention of dimensional ordering. In matlab, you order this way:

double element = array4d[y][x][z][w];

which deifies all convention and creates weird situations where you are sometimes indexing as if row ordered and sometimes column ordered (such as with matrix creation).

In reality, Matlab is the unintuitive one, not Numpy.


Read this article for better insight. Note: Numpy reports the shape of 3D arrays in the order layers, rows, columns.

https://opensourceoptions.com/blog/numpy-array-shapes-and-reshaping-arrays/#:~:text=3D%20arrays,order%20layers%2C%20rows%2C%20columns.


You are right, you are creating a matrix with 2 rows, 3 columns and 4 depth. Numpy prints matrixes different to Matlab:

Numpy:

>>> import numpy as np
>>> np.zeros((2,3,2))
 array([[[ 0.,  0.],
    [ 0.,  0.],
    [ 0.,  0.]],

   [[ 0.,  0.],
    [ 0.,  0.],
    [ 0.,  0.]]])

Matlab

>> zeros(2, 3, 2)
 ans(:,:,1) =
     0     0     0
     0     0     0
 ans(:,:,2) =
     0     0     0
     0     0     0

However you are calculating the same matrix. Take a look to Numpy for Matlab users, it will guide you converting Matlab code to Numpy.


For example if you are using OpenCV, you can build an image using numpy taking into account that OpenCV uses BGR representation:

import cv2
import numpy as np

a = np.zeros((100, 100,3))
a[:,:,0] = 255

b = np.zeros((100, 100,3))
b[:,:,1] = 255

c = np.zeros((100, 200,3)) 
c[:,:,2] = 255

img = np.vstack((c, np.hstack((a, b))))

cv2.imshow('image', img)
cv2.waitKey(0)

enter image description here

If you take a look to matrix c you will see it is a 100x200x3 matrix which is exactly what it is shown in the image (in red as we have set the R coordinate to 255 and the other two remain at 0).


No need to go in such deep technicalities, and get yourself blasted. Let me explain it in the most easiest way. We all have studied "Sets" during our school-age in Mathematics. Just consider 3D numpy array as the formation of "sets".

x = np.zeros((2,3,4)) 

Simply Means:

2 Sets, 3 Rows per Set, 4 Columns

Example:

Input

x = np.zeros((2,3,4))

Output

Set # 1 ---- [[[ 0.,  0.,  0.,  0.],  ---- Row 1
               [ 0.,  0.,  0.,  0.],  ---- Row 2
               [ 0.,  0.,  0.,  0.]], ---- Row 3 
    
Set # 2 ----  [[ 0.,  0.,  0.,  0.],  ---- Row 1
               [ 0.,  0.,  0.,  0.],  ---- Row 2
               [ 0.,  0.,  0.,  0.]]] ---- Row 3

Explanation: See? we have 2 Sets, 3 Rows per Set, and 4 Columns.

Note: Whenever you see a "Set of numbers" closed in double brackets from both ends. Consider it as a "set". And 3D and 3D+ arrays are always built on these "sets".


You have a truncated array representation. Let's look at a full example:

>>> a = np.zeros((2, 3, 4))
>>> a
array([[[ 0.,  0.,  0.,  0.],
        [ 0.,  0.,  0.,  0.],
        [ 0.,  0.,  0.,  0.]],

       [[ 0.,  0.,  0.,  0.],
        [ 0.,  0.,  0.,  0.],
        [ 0.,  0.,  0.,  0.]]])

Arrays in NumPy are printed as the word array followed by structure, similar to embedded Python lists. Let's create a similar list:

>>> l = [[[ 0.,  0.,  0.,  0.],
          [ 0.,  0.,  0.,  0.],
          [ 0.,  0.,  0.,  0.]],

          [[ 0.,  0.,  0.,  0.],
          [ 0.,  0.,  0.,  0.],
          [ 0.,  0.,  0.,  0.]]]

>>> l
[[[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.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]]]

The first level of this compound list l has exactly 2 elements, just as the first dimension of the array a (# of rows). Each of these elements is itself a list with 3 elements, which is equal to the second dimension of a (# of columns). Finally, the most nested lists have 4 elements each, same as the third dimension of a (depth/# of colors).

So you've got exactly the same structure (in terms of dimensions) as in Matlab, just printed in another way.

Some caveats:

  1. Matlab stores data column by column ("Fortran order"), while NumPy by default stores them row by row ("C order"). This doesn't affect indexing, but may affect performance. For example, in Matlab efficient loop will be over columns (e.g. for n = 1:10 a(:, n) end), while in NumPy it's preferable to iterate over rows (e.g. for n in range(10): a[n, :] -- note n in the first position, not the last).

  2. If you work with colored images in OpenCV, remember that:

    2.1. It stores images in BGR format and not RGB, like most Python libraries do.

    2.2. Most functions work on image coordinates (x, y), which are opposite to matrix coordinates (i, j).