|
#!/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., ], ])) |