Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change stride of numpy array (altering data)

I have a numpy array and I want to change its strides while at the same time modifying its data so that the new array describes the same logical alignment of numbers. Is there any way to do that?

BACKGROUND: I read an image file with cv2.imdecode(), which produces a BGR image with a lowest-order stride set to 3 (so no gaps between channels of different pixels). I want to modify this image with the cairo package, which wants to work with stride 4 (that is, a gap of a byte between two consecutive pixels). Which is the best way to do that? (I would also like to be as optimized as possible, as I have to do this many times).

like image 995
Giovanni Mascellani Avatar asked Jul 03 '26 20:07

Giovanni Mascellani


1 Answers

Assuming the input array is (n,m,3), then it can be expanded to (n,m,4) by simply concatenating a (n,m,1) array.

X = np.ones((n,m,3), dtype='byte')
F = np.zeros((n,m,1), dtype='byte')
X1 = np.concatenate([X,F], axis=2)

Strides for these are (3*m,3,1), (m,1,1) and (4*m,4,1).

The same data could put in

In [72]: dt0 = np.dtype([('R','u1'), ('G','u1'), ('B','u1')])
In [73]: X=np.ones((n,m),dtype=dt0)
In [74]: X.strides
Out[74]: (150, 3)
In [75]: X.shape
Out[75]: (30, 50)

With the target having dt = np.dtype([('R','u1'), ('G','u1'), ('B','u1'), ('A','u1')]) http://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html

Mapping RGB fields to RGBA

But to do concatenation with these dtypes, we'd do some sort of casting to the (n,m,3) shape. Looks like reassigning the data attribute will do the trick.

n, m = 2, 4
dt1 = np.dtype([('R','u1'), ('G','u1'), ('B','u1'), ('A','u1')])
dt0 = np.dtype([('R','u1'), ('G','u1'), ('B','u1')])
X = np.arange(n*m*3,dtype='u1').reshape(n,m,3)
print repr(X)
X0 = np.zeros((n,m), dtype=dt0)
X0.data = X.data
print repr(X0)
X0.strides # (12, 3)

X1 = np.zeros((n,m), dtype=dt1)
F = np.zeros((n,m,1), dtype='u1')
X01 = np.concatenate([X, F], axis=2)
X1.data = X01.data
print repr(X1)
X1.strides # (12, 4)

producing:

array([[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11)],
       [(12, 13, 14), (15, 16, 17), (18, 19, 20), (21, 22, 23)]], 
      dtype=[('R', 'u1'), ('G', 'u1'), ('B', 'u1')])
array([[(0, 1, 2, 0), (3, 4, 5, 0), (6, 7, 8, 0), (9, 10, 11, 0)],
       [(12, 13, 14, 0), (15, 16, 17, 0), (18, 19, 20, 0), (21, 22, 23, 0)]], 
      dtype=[('R', 'u1'), ('G', 'u1'), ('B', 'u1'), ('A', 'u1')])

Using overlapping dtypes

Here's a way of doing it with overlapping dtypes rather than concatenation:

dt0 = np.dtype([('R','u1'), ('G','u1'), ('B','u1')])
dt1 = np.dtype([('R','u1'), ('G','u1'), ('B','u1'), ('A','u1')])
dtb = np.dtype({'names':['rgb','rgba'],
                'offsets':[0,0],
                'formats':[dt0, dt1]})
X0 = np.zeros((n,m), dtype=dt0)
X0.data = X.data
X1 = np.zeros((n,m), dtype=dtb)
X1['rgb'][:] = X0
print repr(X1['rgba'])

Or without the individual named byte fields it's even simpler:

dt0 = np.dtype(('u1',(3,)))
dt1 = np.dtype(('u1',(4,)))
dtb = np.dtype({'names':['rgb','rgba'],
                'offsets':[0,0],
                'formats':[dt0, dt1]})
X = np.arange(n*m*3,dtype='u1').reshape(n,m,3)
X1 = np.zeros((n,m), dtype=dtb)
X1['rgb'][:] = X

X1['rgba'] is (n,m,4), with strides (m*4, 4, 1).

X1['rgb'] is (n,m,3), but with the same strides (m*4, 4, 1).

Using as_strided

That difference in shape, but similarity in strides, suggest's using as_strided. Create the empty target array, and use as_strided to select a subset of elements to receive values from X:

X1 = np.zeros((n,m,4),dtype='u1')
np.lib.stride_tricks.as_strided(X1, shape=X.shape, strides=X1.strides)[:] = X
print X1
like image 60
hpaulj Avatar answered Jul 05 '26 09:07

hpaulj