mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-15 11:25:53 +08:00
Merge pull request #953 from syedTabish/Issue949
Update integral image to support nD images
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import numpy as np
|
||||
import collections
|
||||
|
||||
|
||||
def integral_image(x):
|
||||
def integral_image(img):
|
||||
"""Integral image / summed area table.
|
||||
|
||||
The integral image contains the sum of all elements above and to the
|
||||
@@ -13,13 +14,13 @@ def integral_image(x):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : ndarray
|
||||
img : ndarray
|
||||
Input image.
|
||||
|
||||
Returns
|
||||
-------
|
||||
S : ndarray
|
||||
Integral image / summed area table.
|
||||
Integral image/summed area table of same shape as input image.
|
||||
|
||||
References
|
||||
----------
|
||||
@@ -27,44 +28,117 @@ def integral_image(x):
|
||||
ACM SIGGRAPH Computer Graphics, vol. 18, 1984, pp. 207-212.
|
||||
|
||||
"""
|
||||
return x.cumsum(1).cumsum(0)
|
||||
S = img
|
||||
for i in range(img.ndim):
|
||||
S = S.cumsum(axis=i)
|
||||
return S
|
||||
|
||||
|
||||
def integrate(ii, r0, c0, r1, c1):
|
||||
def integrate(ii, start, end, *args):
|
||||
"""Use an integral image to integrate over a given window.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ii : ndarray
|
||||
Integral image.
|
||||
r0, c0 : int or ndarray
|
||||
Top-left corner(s) of block to be summed.
|
||||
r1, c1 : int or ndarray
|
||||
Bottom-right corner(s) of block to be summed.
|
||||
start : List of tuples, each tuple of length equal to dimension of `ii`
|
||||
Coordinates of top left corner of window(s).
|
||||
Each tuple in the list contains the starting row, col, ... index
|
||||
i.e `[(row_win1, col_win1, ...), (row_win2, col_win2,...), ...]`.
|
||||
end : List of tuples, each tuple of length equal to dimension of `ii`
|
||||
Coordinates of bottom right corner of window(s).
|
||||
Each tuple in the list containing the end row, col, ... index i.e
|
||||
`[(row_win1, col_win1, ...), (row_win2, col_win2, ...), ...]`.
|
||||
args: optional
|
||||
For backward compatibility with versions prior to 0.11.
|
||||
The earlier function signature was `integrate(ii, r0, c0, r1, c1)`,
|
||||
where `r0`, `c0` are int(lists) specifying start coordinates
|
||||
of window(s) to be integrated and `r1`, `c1` the end coordinates.
|
||||
|
||||
Returns
|
||||
-------
|
||||
S : scalar or ndarray
|
||||
Integral (sum) over the given window(s).
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> arr = np.ones((5, 6), dtype=np.float)
|
||||
>>> ii = integral_image(arr)
|
||||
>>> integrate(ii, [(1, 0)], [(1, 2)]) # sum from (1,0) -> (1,2)
|
||||
array([ 3.])
|
||||
>>> integrate(ii, [(3, 3)], [(4, 5)]) # sum form (3,3) -> (4,5)
|
||||
array([ 6.])
|
||||
>>> integrate(ii, [(1, 0), (3, 3)], [(1, 2), (4, 5)]) # sum from (1,0) -> (1,2) and (3,3) -> (4,5)
|
||||
array([ 3., 6.])
|
||||
"""
|
||||
if np.isscalar(r0):
|
||||
r0, c0, r1, c1 = [np.asarray([x]) for x in (r0, c0, r1, c1)]
|
||||
rows = 1
|
||||
# handle input from new input format
|
||||
if len(args) == 0:
|
||||
if isinstance(start, collections.Iterable):
|
||||
rows = len(start)
|
||||
start = np.array(start)
|
||||
end = np.array(end)
|
||||
# handle deprecated input format
|
||||
else:
|
||||
if isinstance(start, collections.Iterable):
|
||||
rows = len(start)
|
||||
args = (start, end) + args
|
||||
start = np.array(args[:int(len(args)/2)]).T
|
||||
end = np.array(args[int(len(args)/2):]).T
|
||||
|
||||
S = np.zeros(r0.shape, ii.dtype)
|
||||
total_shape = ii.shape
|
||||
total_shape = np.tile(total_shape, [rows, 1])
|
||||
|
||||
S += ii[r1, c1]
|
||||
# convert negative indices into equivalent positive indices
|
||||
start_negatives = start < 0
|
||||
end_negatives = end < 0
|
||||
start = (start + total_shape) * start_negatives + \
|
||||
start * ~(start_negatives)
|
||||
end = (end + total_shape) * end_negatives + \
|
||||
end * ~(end_negatives)
|
||||
|
||||
good = (r0 >= 1) & (c0 >= 1)
|
||||
S[good] += ii[r0[good] - 1, c0[good] - 1]
|
||||
if np.any((end - start) < 0):
|
||||
raise IndexError('end coordinates must be greater or equal to start')
|
||||
|
||||
good = r0 >= 1
|
||||
S[good] -= ii[r0[good] - 1, c1[good]]
|
||||
# bit_perm is the total number of terms in the expression
|
||||
# of S. For example, in the case of a 4x4 2D image
|
||||
# sum of image from (1,1) to (2,2) is given by
|
||||
# S = + ii[2, 2]
|
||||
# - ii[0, 2] - ii[2, 0]
|
||||
# + ii[0, 0]
|
||||
# The total terms = 4 = 2 ** 2(dims)
|
||||
|
||||
good = c0 >= 1
|
||||
S[good] -= ii[r1[good], c0[good] - 1]
|
||||
S = np.zeros(rows)
|
||||
bit_perm = 2 ** ii.ndim
|
||||
width = len(bin(bit_perm - 1)[2:])
|
||||
|
||||
if S.size == 1:
|
||||
return np.asscalar(S)
|
||||
# Sum of a (hyper)cube, from an integral image is computed using
|
||||
# values at the corners of the cube. The corners of cube are
|
||||
# selected using binary numbers as described in the following example.
|
||||
# In a 3D cube there are 8 corners. The corners are selected using
|
||||
# binary numbers 000 to 111. Each number is called a permutation, where
|
||||
# perm(000) means, select end corner where none of the coordinates
|
||||
# is replaced, i.e ii[end_row, end_col, end_depth]. Similarly, perm(001)
|
||||
# means replace last coordinate by start - 1, i.e
|
||||
# ii[end_row, end_col, start_depth - 1], and so on.
|
||||
# Sign of even permutations is positive, while those of odd is negative.
|
||||
# If 'start_coord - 1' is -ve it is labeled bad and not considered in
|
||||
# the final sum.
|
||||
|
||||
for i in range(bit_perm): # for all permutations
|
||||
# boolean permutation array eg [True, False] for '10'
|
||||
binary = bin(i)[2:].zfill(width)
|
||||
bool_mask = [bit == '1' for bit in binary]
|
||||
|
||||
sign = (-1)**sum(bool_mask) # determine sign of permutation
|
||||
|
||||
bad = [np.any(((start[r] - 1) * bool_mask) < 0)
|
||||
for r in range(rows)] # find out bad start rows
|
||||
|
||||
corner_points = (end * (np.invert(bool_mask))) + \
|
||||
((start - 1) * bool_mask) # find corner for each row
|
||||
|
||||
S += [sign * ii[tuple(corner_points[r])] if(not bad[r]) else 0
|
||||
for r in range(rows)] # add only good rows
|
||||
return S
|
||||
|
||||
@@ -17,15 +17,16 @@ def test_validity():
|
||||
|
||||
|
||||
def test_basic():
|
||||
assert_equal(x[12:24, 10:20].sum(), integrate(s, 12, 10, 23, 19))
|
||||
assert_equal(x[:20, :20].sum(), integrate(s, 0, 0, 19, 19))
|
||||
assert_equal(x[:20, 10:20].sum(), integrate(s, 0, 10, 19, 19))
|
||||
assert_equal(x[10:20, :20].sum(), integrate(s, 10, 0, 19, 19))
|
||||
assert_equal(x[12:24, 10:20].sum(), integrate(s, (12, 10), (23, 19)))
|
||||
assert_equal(x[:20, :20].sum(), integrate(s, (0, 0), (19, 19)))
|
||||
assert_equal(x[:20, 10:20].sum(), integrate(s, (0, 10), (19, 19)))
|
||||
assert_equal(x[10:20, :20].sum(), integrate(s, (10, 0), (19, 19)))
|
||||
|
||||
|
||||
def test_single():
|
||||
assert_equal(x[0, 0], integrate(s, 0, 0, 0, 0))
|
||||
assert_equal(x[10, 10], integrate(s, 10, 10, 10, 10))
|
||||
assert_equal(x[0, 0], integrate(s, (0, 0), (0, 0)))
|
||||
assert_equal(x[10, 10], integrate(s, (10, 10), (10, 10)))
|
||||
|
||||
|
||||
def test_vectorized_integrate():
|
||||
r0 = np.array([12, 0, 0, 10, 0, 10, 30])
|
||||
@@ -40,7 +41,10 @@ def test_vectorized_integrate():
|
||||
x[0,0],
|
||||
x[10, 10],
|
||||
x[30:, 31:].sum()])
|
||||
assert_equal(expected, integrate(s, r0, c0, r1, c1))
|
||||
start_pts = [(r0[i], c0[i]) for i in range(len(r0))]
|
||||
end_pts = [(r1[i], c1[i]) for i in range(len(r0))]
|
||||
assert_equal(expected, integrate(s, r0, c0, r1, c1)) # test deprecated
|
||||
assert_equal(expected, integrate(s, start_pts, end_pts))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user