Skip to content

Instantly share code, notes, and snippets.

@k-sriram
Last active December 28, 2018 13:30
Show Gist options
  • Save k-sriram/8a054c6bc21482263f2bfb0a032d457b to your computer and use it in GitHub Desktop.
Save k-sriram/8a054c6bc21482263f2bfb0a032d457b to your computer and use it in GitHub Desktop.
Module contains functions to merge two images (represented using multi-dimensional numpy arrays) which may be offset with respect to each other.
#!/usr/bin/env python3
# shift_add.py
# Created by Sriram Krishna on 2018-12-21
#
# Copyright 2018 Sriram Krishna
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
# persons to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
Module contains functions to merge two images (represented using multi-dimensional numpy arrays) which may be offset
with respect to each other.
"""
from itertools import product
import numpy as np
def shift_add(
arr1: {'type': 'np.ndarray(dtype=float)', 'help': "The base image. If 'inplace' is True this will be modified"},
arr2: {'type': 'np.ndarray(dtype=float)',
'help': "The image to be added. Should have the same number of dimensions as 'arr1'"
},
shift: {'type': 'Sequence(Indexable|SupportFloat)',
'help': ("offset between images. The pixel 0 in 'arr2' corresponds to this pixel in 'arr1'. Should be a "
"tuple with len(shift) == len(arr2.shape). Should contain int unless 'interp' is defined, then "
"can be float as well.")
} = None,
reversedShift: {'type': 'bool', 'help': ("Whether to reverse the shift vector. Setting it true lets you send"
"offset as (x,y,z) rather than (z,y,x)")
} = True,
inplace: {'type': 'bool', 'help': 'Should the addition be done in-place or in a new array.'} = False,
interp: {'type': "str (None|'bilinear'|'nearest')",
'help': ("If non-integral values offsets are passed, what kind of interpolation to perform. Possible "
"values are: \n"
"None \n"
" No interpolation. Raises an exception if non-indexable offset is given in 'shift'. \n"
"'nearest' \n"
" Use the nearest integral shift. \n"
"'bilinear' \n"
" Perform bilinear approximation.")
}=None,
) -> {'type': 'np.ndarray'}:
"""
Function to merge two offset images. The images are to be represented as a multidimensional numpy array.
An offset between the images can be passed. The offset needs to be of an indexable type.
"""
if __debug__:
if not isinstance(arr1, np.ndarray):
raise TypeError("shift_add: argument 'arr1' must be numpy.ndarray, not '{}'".format(type(arr1)))
elif not isinstance(arr2, np.ndarray):
raise TypeError("shift_add: argument 'arr2' must be numpy.ndarray, not '{}'".format(type(arr2)))
elif not (shift is None or isinstance(shift, np.ndarray) or isinstance(shift, list) or
isinstance(shift, tuple)):
raise TypeError("shift_add: argument 'shift' must be numpy.ndarray, list, or tuple, not '{}'"
.format(type(shift)))
if shift is not None:
for i in range(len(shift)):
if interp is None:
if not type(shift[i]) == int:
try:
shift[i].__index__()
except (AttributeError, TypeError):
raise TypeError('shift_add: all elements of \'shift\' must be indexable (such as int). '
'Element {:d} is {}'.format(i, shift[i]))
else:
if not type(shift[i]) == float:
try:
float(shift[i])
except (ValueError):
raise TypeError('shift_add: all elements of \'shift\' must be support float. '
'Element {:d} is {}'.format(i, shift[i]))
if len(arr1.shape) != len(arr2.shape):
raise ValueError("shift_add: 'arr1' and 'arr2' must have the same number of dimensions. {} != {}"
.format(len(arr1.shape), len(arr2.shape)))
if shift is not None and len(arr2.shape) != len(shift):
raise ValueError("shift_add: 'arr2' must have the same number of dimensions as length of 'shift'. {} != {}"
.format(len(arr2.shape), len(shift)))
if shift is None:
shift = (0,) * len(arr2.shape)
elif reversedShift == True:
shift = tuple(reversed(shift))
if interp == 'nearest':
shift = tuple(int(round(i)) for i in shift)
interp = None
if inplace == False:
arr1 = arr1.copy()
# The function first finds the common window between arr1 and arr2.
# This is stored in oslice and islice. Then just adds the numpy subarrays.
if interp == None:
_shift_add(arr1,arr2,shift)
elif interp == 'bilinear':
ishift = np.floor(shift).astype(int)
for dshift in product((0, 1), repeat=ishift.shape[0]):
dshift = np.array(dshift)
idshift = ishift + dshift
wt = np.product(1-np.absolute(idshift-shift))
_shift_add(arr1, wt*arr2, idshift)
else:
raise ValueError("shift_add: unsupported value for 'interp': {}".format(interp))
return arr1
def _shift_add(outarr,inarr,shift):
oslice, islice = getshiftslices(shift,outarr.shape,inarr.shape)
outarr[oslice] += inarr[islice]
return outarr
def getshiftslices(shift,osh,ish):
oslice = tuple(slice(max(0, +shift[i]), min(osh[i], ish[i]+shift[i])) for i in range(len(osh)))
islice = tuple(slice(max(0, -shift[i]), min(ish[i], osh[i]-shift[i])) for i in range(len(ish)))
return oslice,islice
__all__ = ['shift_add','getshiftslices']
if __name__ == '__main__':
foo = np.arange(1, 10,dtype=float).reshape((3, 3))
bar = np.arange(100, 500, 100,dtype=float).reshape((2, 2))
assert np.all(shift_add(foo, bar) == np.array([ [ 101., 202., 3., ],
[ 304., 405., 6., ],
[ 7., 8., 9., ], ]))
shift_add(foo, bar, (2, 1), inplace=True)
assert np.all(foo == np.array([ [ 1., 2., 3., ],
[ 4., 5., 106., ],
[ 7., 8., 309., ], ]))

shift_add

Module contains functions to merge two images (represented using multi-dimensional numpy arrays) which may be offset with respect to each other.

Contains

  • shift_add
  • getshiftslices

shift_add(arr1, arr2, shift=False, reversedShift=None, inplace=None, interp=True)

Function to merge two offset images. The images are to be represented as a multidimensional numpy array. An offset between the images can be passed. The offset needs to be of an indexable type.

Parameters

arr1 : np.ndarray(dtype=float)
The base image. If 'inplace' is True this will be modified
arr2 : np.ndarray(dtype=float)
The image to be added. Should have the same number of dimensions as 'arr1'
shift : Sequence(Indexable|SupportFloat)
offset between images. The pixel 0 in 'arr2' corresponds to this pixel in 'arr1'. Should be a tuple with len(shift) == len(arr2.shape). Should contain int unless 'interp' is defined, then can be float as well.
reversedShift : bool
Whether to reverse the shift vector. Setting it true lets you sendoffset as (x,y,z) rather than (z,y,x)
inplace : bool
Should the addition be done in-place or in a new array.
interp : str (None|'bilinear'|'nearest')

If non-integral values offsets are passed, what kind of interpolation to perform. Possible values are:

None
No interpolation. Raises an exception if non-indexable offset is given in 'shift'.
'nearest'
Use the nearest integral shift.
'bilinear'
Perform bilinear approximation.

Returns

np.ndarray

  1. A more efficient implementation for when len(arr1)==2.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment