"""
The R2labMap class is a convenience for mapping node numbers to a
2-dimensional grid coordinates, and backwards.
"""
[docs]
class R2labMapGeneric:
"""
The most general form allows to specifiy
mapper functions in both directions.
Or to simply specify an integer offset and boolean swap.
"""
# pylint: disable=c0326
POSITIONS = [
[1, 6, 11, 16, 19, 23, 26, 31, None],
[2, 7, 12, None, 20, None, 27, 32, None],
[3, 8, 13, 17, 21, 24, 28, 33, None],
[4, 9, 14, 18, 22, 25, 29, 34, 36],
[5, 10, 15, None, None, None, 30, 35, 37]
]
# pylint: enable=c0326
@staticmethod
def _width():
return len(R2labMapGeneric.POSITIONS[0])
@staticmethod
def _height():
return len(R2labMapGeneric.POSITIONS)
def __init__(self, *,
offset_x=0, offset_y=0,
swap_x=False, swap_y=False,
map_x=None, map_y=None):
"""
By default, node 1 is on coordinates (0, 0),
and node 37 is at (8, 4).
The parameters allow to implement other mappings.
Parameters:
map_x(function): function that maps integers to integers;
if provided, the `swap_x` and `offset_x` are ignored;
swap_x(bool): if set, the x axis is reversed; that is,
with only swap_x set, node 1 becomes (8, 0) and
node 37 becomes (0, 4)
offset_x(int): added in the X direction
And likewise in the Y dimension.
Examples:
This means that typically map functions need be something
like:
* ``map_x = lambda x: x+1`` if you want to start numbering at 1
* map_y = lambda y: 4-x if you want to have y go upwards
* map_y = lambda y: 5-y for same direction but start at 1
"""
# most general form if for you to provide the map function
if map_x:
self.map_x = map_x
# otherwise you can provide swap_x and/or offset_x
elif swap_x:
self.map_x = lambda x: offset_x + self._width() - 1 - x
else:
self.map_x = lambda x: offset_x + x
# same in y
if map_y:
self.map_y = map_y
elif swap_y:
self.map_y = lambda y: offset_y + self._height() - 1 - y
else:
self.map_y = lambda y: offset_y + y
# computes a dictionary that maps
# a node_id to a tuple of coords (x, y)
self.node_to_position = {
node_id: (self.map_x(x), self.map_y(y))
for y, line in enumerate(self.POSITIONS)
for x, node_id in enumerate(line)
if node_id
}
# reverse dict (x, y) -> node
self.position_to_node = {
(self.map_x(x), self.map_y(y)): node_id
for (node_id, (x, y)) in self.node_to_position.items()
}
[docs]
def indexes(self):
"""
Object that can be used to create a pandas index
on the nodes; essentially this is range(1, 38)
"""
return sorted(self.node_to_position.keys())
[docs]
def position(self, node):
"""
Returns a (x, y) tuple that is the position of node <node>
Parameters:
node: a node number - may be an int or a str
Returns:
(int, int): a position on the grid
"""
node = int(node)
return self.node_to_position[node]
[docs]
def node(self, x, y):
"""
Finds about the node at that position
Parameters:
x: coordinate along the horizontal axis - int or str
y: coordinate along the vertical axis - int or str
Returns:
int: a node number, in the range (1..37) - or None
"""
return self.position_to_node.get((x, y), None)
[docs]
def iterate_nodes(self):
"""
An iterator that yields 37 tuples of the form
(node_id, (x, y))
"""
return self.node_to_position.items()
[docs]
def iterate_holes(self):
"""
An iterator that yields tuples of the form (x, y) for all the
possible (x, y) that do not match a node
"""
for y, line in enumerate(self.POSITIONS):
for x, node_id in enumerate(line):
if not node_id:
yield self.map_x(x), self.map_y(y)
[docs]
class R2labMap(R2labMapGeneric):
"""
Inherits :class:`~r2lab.r2labmap.R2labMapGeneric`
A map object where coordinates start at 1,
and where the Y coordinate goes upwards;
so typically in this map node 1 is at (1, 5)
and node 37 is at (9, 1).
"""
def __init__(self):
super().__init__(offset_x=1, offset_y=1, swap_y=True)