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).
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
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')])
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).
as_stridedThat 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With