r
/
fifth
1
Fork 0

Basic neighboorhood

master
jrpotter 2015-05-31 17:35:41 -04:00
parent cfb43944d3
commit 0113ee34ae
1 changed files with 128 additions and 34 deletions

View File

@ -2,36 +2,7 @@
""" """
import enum import itertools
class Cells(enum.Enum):
"""
Allows for specification of which cells should be considered in a 2D or 3D matrix.
For example, to specify the CENTER and NORTH cells, we consider CENTER | NORTH. It
does not make sense to use FORWARD and BACKWARD in a 2D matrix, as that specifies
looking up and down a bitplane for further cells.
For higher level dimensions, forgo use of this enumeration entirely, as described in
the Neighborhood class.
"""
# 2D & 3D
CENTER = 1 << 0
NORTH = 1 << 1
NORTHEAST = 1 << 2
EAST = 1 << 3
SOUTHEAST = 1 << 4
SOUTH = 1 << 5
SOUTHWEST = 1 << 6
WEST = 1 << 7
NORTHWEST = 1 << 8
NORTH = 1 << 9
# 3D
FORWARD = 1 << 10
BACKWARD = 1 << 11
class Neighborhood: class Neighborhood:
@ -42,13 +13,136 @@ class Neighborhood:
the basic Moore neighborhood comprises of the 8 cells surrounding the center, but what if we wanted the basic Moore neighborhood comprises of the 8 cells surrounding the center, but what if we wanted
these 8 and include the cell north of north? The following enables this: these 8 and include the cell north of north? The following enables this:
... m_neighborhood = Neighborhood.moore(2)
m_neighborhood.extend({(-2, 0): True})
This allows indexing at levels beyond 3D, which the Cells enumeration does not allow, though visualization This allows indexing at levels beyond 3D, which the Cells enumeration does not allow, though visualization
at this point isn't possible. at this point isn't possible.
""" """
def __init__(self, grid):
pass class NeighborhoodKey:
"""
Allows proper sorting of neighborhoods.
Lists should be returned in order, where cell's with smaller indices (in most significant axis first)
are listed before cell's with larger ones. For example, in a 3D grid, the neighbors corresponding to:
offsets = (-1, -1, -1), (-1, 1, 0), (-1, 0, -1), and (1, 0, -1)
are returned in the following order:
offsets = (-1, -1, -1), (-1, 0, -1), (1, 0, -1), (-1, 1, 0)
since the z-axis is most significant, followed by the y-axis, and lastly the x-axis.
"""
def __init__(self, obj, *args):
self.obj = obj
def __lt__(self, other):
return self.compare(self.obj, other.obj) < 0
def __gt__(self, other):
return self.compare(self.obj, other.obj) > 0
def __eq__(self, other):
return self.compare(self.obj, other.obj) == 0
def __le__(self, other):
return self.compare(self.obj, other.obj) <= 0
def __ge__(self, other):
return self.compare(self.obj, other.obj) >= 0
def __ne__(self, other):
return self.compare(self.obj, other.obj) != 0
def compare(self, other):
for i in reversed(range(len(a))):
if a[i] < b[i]:
return -1
elif a[i] > b[i]:
return 1
return 0
def __init__(self):
"""
Sets up an empty neighborhood.
Initially, no cells are included in a neighborhood. All neighborhoods must be extended.
Note the offsets have a tuple as a key representing the position being offsetted by, and as a value,
the current state the given cell at the offset is checked to be.
"""
self.offsets = {}
def neighbors(self, bit, grid):
"""
Returns all bits in the given neighborhood.
The returned cells are grouped with the value the cell is checked to be (a 2-tuple (Bit, value) pair).
These are sorted based on the NeighborhoodKey comparison class defined above.
"""
bits = []
for k in sorted(self.offsets.keys()):
bits.append((k, self.offsets[k]))
return bits
def extend(self, offsets, strict=False):
"""
Adds new offsets to the instance member offsets.
We complain if the strict flag is set to True and an offset has already been declared with a different value.
"""
if not strict:
self.offsets.update(offsets)
else:
for k in offsets.keys():
value = self.offsets.get(k, None)
if value is None:
self.offsets[k] = offsets[k]
elif value != offsets[k]:
raise KeyError
@classmethod
def moore(cls, dimen, value=True):
"""
Returns a neighborhood corresponding to the Moore neighborhood.
The Moore neighborhood consists of all adjacent cells. In 2D, these correspond to the 8 touching cells
N, NE, E, SE, S, SW, S, and NW. In 3D, this corresponds to all cells in the "backward" and "forward"
layer that adjoin the nine cells in the "center" layer. This concept can be extended to N dimensions.
Note the center cell is excluded, so the total number of offsets are 3^N - 1.
"""
offsets = {}
variants = ([-1, 0, 1],) * dimen
for current in itertools.product(*variants):
if any(current):
offsets[current] = value
m_neighborhood = cls()
m_neighborhood.extend(offsets)
return m_neighborhood
@classmethod
def neumann(cls, dimen, value=True):
"""
Returns a neighborhood corresponding to the Von Neumann neighborhood.
The Von Neumann neighborhood consists of adjacent cells that directly share a face with the current cell.
In 2D, these correspond to the 4 touching cells N, S, E, W. In 3D, we include the "backward" and "forward"
cell. This concept can be extended to N dimensions.
Note the center cell is excluded, so the total number of offsets are 2N.
"""
offsets = {}
variant = [0] * dimen
for i in range(len(variant)):
for j in [-1, 1]:
variant[i] = j
offsets[tuple(variant)] = value
variant[i] = 0
n_neighborhood = cls()
n_neighborhood.extend(offsets)
return n_neighborhood