Shifting an image in numpy
Question:
I have a image in a 2d numpy array. I want to shift the image by an X and Y offset and want the rest of the frame padded with zeros. I have seen discussions about the ‘roll’ function but that only works in 1 axis. (unless someone can point me to a 2d version with padding). I have tried slicing but I run into trouble when shifting offsets have all possible directions. I don’t want to navigate through all X Y offset +/- permutations. Is there a simple general solution? I have the below code which works nice for X-offset=+100. But it crashes for X-offset=-100.
Thanks,
Gert
import matplotlib.pyplot as plt
import scipy.misc as msc
import numpy as np
lena = msc.lena()
lena.dtype
(imx,imy)= lena.shape
ox= 100
oy= 20
shift_lena = np.zeros((imx,imy))
shift_lena[0:imy-oy,0:imx-ox] = lena[oy:,ox:]
shift_lena_m = shift_lena.astype(np.int64)
shift_lena_m.dtype
plt.figure(figsize=(10, 3.6))
plt.subplot(131)
plt.imshow(lena, cmap=plt.cm.gray)
plt.subplot(132)
plt.imshow(shift_lena_m, cmap=plt.cm.gray)
plt.subplots_adjust(wspace=0, hspace=0., top=0.99, bottom=0.01, left=0.05, right=0.99)
plt.show()
Answers:
There’s no other way, as to handle negative and positive shifts accordingly:
non = lambda s: s if s<0 else None
mom = lambda s: max(0,s)
ox, oy = 100, 20
shift_lena = numpy.zeros_like(lena)
shift_lena[mom(oy):non(oy), mom(ox):non(ox)] = lena[mom(-oy):non(-oy), mom(-ox):non(-ox)]
You can use roll function to circular shift x and y and then zerofill the offset
def shift_image(X, dx, dy):
X = np.roll(X, dy, axis=0)
X = np.roll(X, dx, axis=1)
if dy>0:
X[:dy, :] = 0
elif dy<0:
X[dy:, :] = 0
if dx>0:
X[:, :dx] = 0
elif dx<0:
X[:, dx:] = 0
return X
For shifting along a specific axis, for integer and non-integer shifts, you may use:
def shift_img_along_axis( img, axis=0, shift = 1 , constant_values=0):
""" shift array along a specific axis. New value is taken as weighted by the two distances to the assocaited original pixels.
CHECKED : Works for floating shift ! ok.
NOTE: at the border of image, when not enough original pixel is accessible, data will be meaned with regard to additional constant_values.
constant_values: value to set to pixels with no association in original image img
RETURNS : shifted image.
A.Mau. """
intshift = int(shift)
remain0 = abs( shift - int(shift) )
remain1 = 1-remain0 #if shift is uint : remain1=1 and remain0 =0
npad = int( np.ceil( abs( shift ) ) ) #ceil relative to 0. ( 0.5=> 1 and -0.5=> -1 )
pad_arg = [(0,0)]*img.ndim
pad_arg[axis] = (npad,npad)
bigger_image = np.pad( img, pad_arg, 'constant', constant_values=constant_values)
part1 = remain1*bigger_image.take( np.arange(npad+intshift, npad+intshift+img.shape[axis]) ,axis)
if remain0==0:
shifted = part1
else:
if shift>0:
part0 = remain0*bigger_image.take( np.arange(npad+intshift+1, npad+intshift+1+img.shape[axis]) ,axis)
else:
part0 = remain0*bigger_image.take( np.arange(npad+intshift-1, npad+intshift-1+img.shape[axis]) ,axis)
shifted = part0 + part1
return shifted
A quick example :
np.random.seed(1)
img = np.random.uniform(0,10,(3,4)).astype('int')
print( img )
shift = 1.5
shifted = shift_img_along_axis( img, axis=1, shift=shift )
print( shifted )
Image print :
[[4 7 0 3]
[1 0 1 3]
[3 5 4 6]]
Shifted image:
[[3.5 1.5 1.5 0. ]
[0.5 2. 1.5 0. ]
[4.5 5. 3. 0. ]]
With our shift of 1.5 the first value in shifted image is the mean of 7 and 0, and so on… If a value is missing in the original image an additionnal value of 0 will be taken.
I have a image in a 2d numpy array. I want to shift the image by an X and Y offset and want the rest of the frame padded with zeros. I have seen discussions about the ‘roll’ function but that only works in 1 axis. (unless someone can point me to a 2d version with padding). I have tried slicing but I run into trouble when shifting offsets have all possible directions. I don’t want to navigate through all X Y offset +/- permutations. Is there a simple general solution? I have the below code which works nice for X-offset=+100. But it crashes for X-offset=-100.
Thanks,
Gert
import matplotlib.pyplot as plt
import scipy.misc as msc
import numpy as np
lena = msc.lena()
lena.dtype
(imx,imy)= lena.shape
ox= 100
oy= 20
shift_lena = np.zeros((imx,imy))
shift_lena[0:imy-oy,0:imx-ox] = lena[oy:,ox:]
shift_lena_m = shift_lena.astype(np.int64)
shift_lena_m.dtype
plt.figure(figsize=(10, 3.6))
plt.subplot(131)
plt.imshow(lena, cmap=plt.cm.gray)
plt.subplot(132)
plt.imshow(shift_lena_m, cmap=plt.cm.gray)
plt.subplots_adjust(wspace=0, hspace=0., top=0.99, bottom=0.01, left=0.05, right=0.99)
plt.show()
There’s no other way, as to handle negative and positive shifts accordingly:
non = lambda s: s if s<0 else None
mom = lambda s: max(0,s)
ox, oy = 100, 20
shift_lena = numpy.zeros_like(lena)
shift_lena[mom(oy):non(oy), mom(ox):non(ox)] = lena[mom(-oy):non(-oy), mom(-ox):non(-ox)]
You can use roll function to circular shift x and y and then zerofill the offset
def shift_image(X, dx, dy):
X = np.roll(X, dy, axis=0)
X = np.roll(X, dx, axis=1)
if dy>0:
X[:dy, :] = 0
elif dy<0:
X[dy:, :] = 0
if dx>0:
X[:, :dx] = 0
elif dx<0:
X[:, dx:] = 0
return X
For shifting along a specific axis, for integer and non-integer shifts, you may use:
def shift_img_along_axis( img, axis=0, shift = 1 , constant_values=0):
""" shift array along a specific axis. New value is taken as weighted by the two distances to the assocaited original pixels.
CHECKED : Works for floating shift ! ok.
NOTE: at the border of image, when not enough original pixel is accessible, data will be meaned with regard to additional constant_values.
constant_values: value to set to pixels with no association in original image img
RETURNS : shifted image.
A.Mau. """
intshift = int(shift)
remain0 = abs( shift - int(shift) )
remain1 = 1-remain0 #if shift is uint : remain1=1 and remain0 =0
npad = int( np.ceil( abs( shift ) ) ) #ceil relative to 0. ( 0.5=> 1 and -0.5=> -1 )
pad_arg = [(0,0)]*img.ndim
pad_arg[axis] = (npad,npad)
bigger_image = np.pad( img, pad_arg, 'constant', constant_values=constant_values)
part1 = remain1*bigger_image.take( np.arange(npad+intshift, npad+intshift+img.shape[axis]) ,axis)
if remain0==0:
shifted = part1
else:
if shift>0:
part0 = remain0*bigger_image.take( np.arange(npad+intshift+1, npad+intshift+1+img.shape[axis]) ,axis)
else:
part0 = remain0*bigger_image.take( np.arange(npad+intshift-1, npad+intshift-1+img.shape[axis]) ,axis)
shifted = part0 + part1
return shifted
A quick example :
np.random.seed(1)
img = np.random.uniform(0,10,(3,4)).astype('int')
print( img )
shift = 1.5
shifted = shift_img_along_axis( img, axis=1, shift=shift )
print( shifted )
Image print :
[[4 7 0 3]
[1 0 1 3]
[3 5 4 6]]
Shifted image:
[[3.5 1.5 1.5 0. ]
[0.5 2. 1.5 0. ]
[4.5 5. 3. 0. ]]
With our shift of 1.5 the first value in shifted image is the mean of 7 and 0, and so on… If a value is missing in the original image an additionnal value of 0 will be taken.