Source code for markovchain.image.traversal

from abc import abstractmethod
from math import ceil, log2

from ..util import SaveLoad, load


[docs]class Traversal(SaveLoad): """Base image traversal class. Attributes ---------- classes : `dict` Image traversal class group. """ classes = {}
[docs] @abstractmethod def __call__(self, width, height, ends=True): """Traverse an image. Parameters ---------- width : `int` Image width. height : `int` Image height. ends : `bool`, optional Generate block ends (default: `True`). Raises ------ NotImplementedError Returns ------- `generator` of ((`int`, `int`) or `None`) Points and block ends. """ pass
[docs]class Lines(Traversal): # pylint: disable=abstract-method """Base line traversal class. Attributes ---------- reverse : `int` If greater than 0, reverse every nth line. line_sentences : `bool` Generate a block end after each line. """
[docs] def __init__(self, reverse=0, line_sentences=False): """Base line traversal constructor. Parameters ---------- reverse : `int`, optional If greater than 0, reverse every nth line (default: 0). line_sentences : `bool`, optional Generate a block end after each line (default: `False`). """ super().__init__() self.reverse = reverse self.line_sentences = line_sentences
def __eq__(self, t): return (self.reverse == t.reverse and self.line_sentences == t.line_sentences)
[docs] def save(self): """Convert to JSON. Returns ------- `dict` JSON data. """ data = super().save() data['reverse'] = self.reverse data['line_sentences'] = self.line_sentences return data
[docs]class HLines(Lines): """Horizontal line traversal. Examples -------- >>> traverse = HLines() >>> list(traverse(2, 2)) [(0, 0), (1, 0), (0, 1), (1, 1)] >>> traverse = HLines(reverse=1) >>> list(traverse(2, 2)) [(1, 0), (0, 0), (1, 1), (0, 1)] >>> traverse = HLines(reverse=2) >>> list(traverse(2, 2)) [(1, 0), (0, 0), (0, 1), (1, 1)] >>> traverse = HLines(line_sentences=True) >>> list(traverse(2, 2)) [(0, 0), (1, 0), None, (0, 1), (1, 1), None] >>> list(traverse(2, 2, ends=False)) [(0, 0), (1, 0), (0, 1), (1, 1)] """
[docs] def __call__(self, width, height, ends=True): """Traverse an image. Parameters ---------- width : `int` Image width. height : `int` Image height. ends : `bool`, optional Generate block ends (default: `True`). Returns ------- `generator` of ((`int`, `int`) or `None`) Points and block ends. """ for y in range(height): if self.reverse > 0 and y % self.reverse == 0: xs = range(width - 1, -1, -1) else: xs = range(width) for x in xs: yield x, y if self.line_sentences and ends: yield None
[docs]class VLines(Lines): """Vertical line traversal. Examples -------- >>> traverse = VLines() >>> list(traverse(2, 2)) [(0, 0), (0, 1), (1, 0), (1, 1)] >>> traverse = VLines(reverse=1) >>> list(traverse(2, 2)) [(0, 1), (0, 0), (1, 1), (1, 0)] >>> traverse = VLines(reverse=2) >>> list(traverse(2, 2)) [(1, 0), (0, 0), (1, 0), (1, 1)] >>> traverse = VLines(line_sentences=True) >>> list(traverse(2, 2)) [(0, 0), (0, 1), None, (1, 0), (1, 1), None] >>> list(traverse(2, 2, ends=False)) [(0, 0), (0, 1), (1, 0), (1, 1)] """
[docs] def __call__(self, width, height, ends=True): """Traverse an image. Parameters ---------- width : `int` Image width. height : `int` Image height. ends : `bool`, optional Generate block ends (default: `True`). Returns ------- generator of ((`int`, `int`) or `None`) Points and block ends. """ for x in range(width): if self.reverse > 0 and x % self.reverse == 0: ys = range(height - 1, -1, -1) else: ys = range(height) for y in ys: yield x, y if self.line_sentences and ends: yield None
[docs]class Spiral(Traversal): """Spiral traversal. Attributes ---------- reverse : `bool` Reverse traversal order. Examples -------- >>> traverse = Spiral() >>> list(traverse(3, 3)) [(1, 1), (0, 1), (0, 2), (1, 2), (2, 2), (2, 1), (2, 0), (1, 0), (0, 0)] >>> traverse = Spiral(True) >>> list(traverse(3, 3)) [(0, 0), (1, 0), (2, 0), (2, 1), (2, 2), (1, 2), (0, 2), (0, 1), (1, 1)] """
[docs] def __init__(self, reverse=False): """Spiral traversal constructor. Parameters ---------- reverse : `bool`, optional Reverse traversal order (default: `False`). """ super().__init__() self.reverse = reverse
def __eq__(self, t): return self.reverse == t.reverse
[docs] def save(self): """Convert to JSON. Returns ------- `dict` JSON data. """ data = super().save() data['reverse'] = self.reverse return data
@staticmethod def _rspiral(width, height): """Reversed spiral generator. Parameters ---------- width : `int` Spiral width. height : `int` Spiral height. Returns ------- `generator` of (`int`, `int`) Points. """ x0 = 0 y0 = 0 x1 = width - 1 y1 = height - 1 while x0 < x1 and y0 < y1: for x in range(x0, x1): yield x, y0 for y in range(y0, y1): yield x1, y for x in range(x1, x0, -1): yield x, y1 for y in range(y1, y0, -1): yield x0, y x0 += 1 y0 += 1 x1 -= 1 y1 -= 1 if x0 == x1: for y in range(y0, y1 + 1): yield x0, y elif y0 == y1: for x in range(x0, x1 + 1): yield x, y0 @staticmethod def _spiral(width, height): """Spiral generator. Parameters ---------- width : `int` Spiral width. height : `int` Spiral height. Returns ------- `generator` of (`int`, `int`) Points. """ if width == 1: for y in range(height - 1, -1, -1): yield 0, y return if height == 1: for x in range(width - 1, -1, -1): yield x, 0 return if width <= height: x0 = width // 2 if width % 2: for y in range(height - 1 - x0, x0 - 1, -1): yield x0, y x0 -= 1 y0 = x0 else: y0 = height // 2 if height % 2: for x in range(width - 1 - y0, y0 - 1, -1): yield x, y0 y0 -= 1 x0 = y0 while x0 >= 0: x1 = width - x0 - 1 y1 = height - y0 - 1 for y in range(y0 + 1, y1): yield x0, y for x in range(x0, x1): yield x, y1 for y in range(y1, y0, -1): yield x1, y for x in range(x1, x0 - 1, -1): yield x, y0 x0 -= 1 y0 -= 1
[docs] def __call__(self, width, height, ends=True): # pylint: disable=unused-argument """Traverse an image. Parameters ---------- width : `int` Image width. height : `int` Image height. ends : `bool`, optional Unused (default: `True`). Returns ------- `generator` of (`int`, `int`) Points. """ if self.reverse: yield from self._rspiral(width, height) else: yield from self._spiral(width, height)
[docs]class Hilbert(Traversal): """Hilbert curve traversal. Attributes ---------- POSITION : `list` of (`int`, `int`) Block positions. Examples -------- >>> traverse = Hilbert() >>> list(traverse(2, 2)) [(0, 0), (0, 1), (1, 1), (1, 0)] >>> list(traverse(3, 5)) [(0, 0), (0, 1), (1, 1), (1, 0), (2, 0), (2, 1), (2, 2), (2, 3), (1, 3), (1, 2), (0, 2), (0, 3), (0, 4), (1, 4), (2, 4)] """ POSITION = [(0, 0), (0, 1), (1, 1), (1, 0)]
[docs] @classmethod def get_point_in_block(cls, x, y, block_idx, block_size): """Get point coordinates in next block. Parameters ---------- x : `int` X coordinate in current block. y : `int` Y coordinate in current block. block_index : `int` Current block index in next block. block_size : `int` Current block size. Raises ------ IndexError If block index is out of range. Returns ------- (`int`, `int`) Point coordinates. """ if block_idx == 0: return y, x if block_idx == 1: return x, y + block_size if block_idx == 2: return x + block_size, y + block_size if block_idx == 3: x, y = block_size - 1 - y, block_size - 1 - x return x + block_size, y raise IndexError('block index out of range: %d' % block_idx)
[docs] @classmethod def get_point(cls, idx, size): """Get curve point coordinates by index. Parameters ---------- idx : `int` Point index. size : `int` Curve size. Returns ------- (`int`, `int`) Point coordinates. """ x, y = cls.POSITION[idx % 4] idx //= 4 block_size = 2 while block_size < size: block_idx = idx % 4 x, y = cls.get_point_in_block(x, y, block_idx, block_size) idx //= 4 block_size *= 2 return x, y
[docs] def __call__(self, width, height, ends=True): # pylint: disable=unused-argument """Traverse an image. Parameters ---------- width : `int` Image width. height : `int` Image height. ends : `bool`, optional Unused (default: `True`). Returns ------- `generator` of (`int`, `int`) Points. """ size = max(width, height) size = 2 ** ceil(log2(size)) generated = 0 points = width * height if generated >= points or width <= 0 or height <= 0: return for i in range(size * size): x, y = self.get_point(i, size) if x < width and y < height: yield x, y generated += 1 if generated >= points: return
def __eq__(self, tr): return isinstance(tr, self.__class__)
[docs]class Blocks(Traversal): """Block traversal. Attributes ---------- block_size : (`int`, `int`) Block size. block_sentences : `bool` Generate a block end after each block. traverse_image : `markovchain.image.traversal.Traversal` Image traversal. traverse_block : `markovchain.image.traversal.Traversal` Block traversal. Examples -------- >>> traverse = Blocks(block_size=(2, 2), ... traverse_image=HLines(), ... traverse_block=VLines()) >>> list(traverse(4, 4)) [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1), (3, 0), (3, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 2), (2, 3), (3, 2), (3, 3)] """
[docs] def __init__(self, block_size=(16, 16), block_sentences=False, traverse_image=None, traverse_block=None): """ Block traversal constructor. Parameters ---------- block_size : (`int`, `int`), optional Block size (default: (16, 16)). block_sentences : `bool`, optional Generate a block end after each block (default: False). traverse_image : `markovchain.image.traversal.Traversal` \ or `dict`, optional Image traversal object or JSON data (default: HLines()). traverse_block : `markovchain.image.traversal.Traversal` \ or `dict`, optional Block traversal object or JSON data (default: HLines()). """ super().__init__() self.block_size = tuple(block_size) self.block_sentences = block_sentences self.traverse_image = load(traverse_image, Traversal, HLines) self.traverse_block = load(traverse_block, Traversal, HLines)
[docs] def __call__(self, width, height, ends=True): """Traverse an image. Parameters ---------- width : `int` Image width. height : `int` Image height. ends : `bool`, optional Generate block ends (default: `True`). Returns ------- `generator` of ((`int`, `int`) or `None`) Points and block ends. """ block_width, block_height = self.block_size img_width = width // block_width img_height = height // block_height for xy in self.traverse_image(img_width, img_height, ends): if xy is None: yield None continue x, y = xy x, y = x * block_width, y * block_height for dx, dy in self.traverse_block(block_width, block_height, False): yield x + dx, y + dy if self.block_sentences and ends: yield None
[docs] def save(self): """Convert to JSON. Returns ------- `dict` JSON data. """ data = super().save() data['block_size'] = list(self.block_size) data['block_sentences'] = self.block_sentences data['traverse_image'] = self.traverse_image.save() data['traverse_block'] = self.traverse_block.save() return data
def __eq__(self, t): return (self.block_size == t.block_size and self.block_sentences == t.block_sentences and self.traverse_image == t.traverse_image and self.traverse_block == t.traverse_block)