Basic neighboorhood
parent
cfb43944d3
commit
0113ee34ae
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue