Move LayoutConfig and RendererLayout to new file

pull/357/head
nyanpasu64 2018-11-03 22:53:07 -07:00
rodzic 62e8c800f4
commit 342752febf
5 zmienionych plików z 171 dodań i 165 usunięć

87
ovgenpy/layout.py 100644
Wyświetl plik

@ -0,0 +1,87 @@
from typing import Optional, TypeVar, Callable, List
import numpy as np
from ovgenpy.config import register_config
from ovgenpy.util import ceildiv
@register_config(always_dump='orientation')
class LayoutConfig:
orientation: str = 'h'
nrows: Optional[int] = None
ncols: Optional[int] = None
def __post_init__(self):
if not self.nrows:
self.nrows = None
if not self.ncols:
self.ncols = None
if self.nrows and self.ncols:
raise ValueError('cannot manually assign both nrows and ncols')
if not self.nrows and not self.ncols:
self.ncols = 1
Region = TypeVar('Region')
RegionFactory = Callable[[int, int], Region] # f(row, column) -> Region
class RendererLayout:
VALID_ORIENTATIONS = ['h', 'v']
def __init__(self, cfg: LayoutConfig, nplots: int):
self.cfg = cfg
self.nplots = nplots
# Setup layout
self.nrows, self.ncols = self._calc_layout()
self.orientation = cfg.orientation
if self.orientation not in self.VALID_ORIENTATIONS:
raise ValueError(f'Invalid orientation {self.orientation} not in '
f'{self.VALID_ORIENTATIONS}')
def _calc_layout(self):
"""
Inputs: self.cfg, self.waves
:return: (nrows, ncols)
"""
cfg = self.cfg
if cfg.nrows:
nrows = cfg.nrows
if nrows is None:
raise ValueError('invalid cfg: rows_first is True and nrows is None')
ncols = ceildiv(self.nplots, nrows)
else:
ncols = cfg.ncols
if ncols is None:
raise ValueError('invalid cfg: rows_first is False and ncols is None')
nrows = ceildiv(self.nplots, ncols)
return nrows, ncols
def arrange(self, region_factory: RegionFactory) -> List[Region]:
""" Generates an array of regions.
index, row, column are fed into region_factory in a row-major order [row][col].
The results are possibly reshaped into column-major order [col][row].
"""
nspaces = self.nrows * self.ncols
inds = np.arange(nspaces)
rows, cols = np.unravel_index(inds, (self.nrows, self.ncols))
row_col = list(zip(rows, cols))
regions = np.empty(len(row_col), dtype=object) # type: np.ndarray[Region]
regions[:] = [region_factory(*rc) for rc in row_col]
regions2d = regions.reshape((self.nrows, self.ncols)) # type: np.ndarray[Region]
# if column major:
if self.orientation == 'v':
regions2d = regions2d.T
return regions2d.flatten()[:self.nplots].tolist()

Wyświetl plik

@ -8,7 +8,8 @@ from typing import Optional, List, Union, TYPE_CHECKING
from ovgenpy import outputs
from ovgenpy.channel import Channel, ChannelConfig
from ovgenpy.config import register_config, register_enum, Ignored
from ovgenpy.renderer import MatplotlibRenderer, RendererConfig, LayoutConfig
from ovgenpy.renderer import MatplotlibRenderer, RendererConfig
from ovgenpy.layout import LayoutConfig
from ovgenpy.triggers import ITriggerConfig, CorrelationTriggerConfig, PerFrameCache, \
LocalPostTriggerConfig
from ovgenpy.util import pushd, coalesce

Wyświetl plik

@ -1,11 +1,12 @@
from typing import Optional, List, TYPE_CHECKING, TypeVar, Callable, Any
from typing import Optional, List, TYPE_CHECKING, Any
import matplotlib
import numpy as np
from ovgenpy.config import register_config
from ovgenpy.layout import RendererLayout
from ovgenpy.outputs import RGB_DEPTH
from ovgenpy.util import ceildiv, coalesce
from ovgenpy.util import coalesce
matplotlib.use('agg')
from matplotlib import pyplot as plt
@ -180,82 +181,3 @@ class MatplotlibRenderer:
return buffer_rgb
@register_config(always_dump='orientation')
class LayoutConfig:
orientation: str = 'h'
nrows: Optional[int] = None
ncols: Optional[int] = None
def __post_init__(self):
if not self.nrows:
self.nrows = None
if not self.ncols:
self.ncols = None
if self.nrows and self.ncols:
raise ValueError('cannot manually assign both nrows and ncols')
if not self.nrows and not self.ncols:
self.ncols = 1
Region = TypeVar('Region')
RegionFactory = Callable[[int, int], Region] # f(row, column) -> Region
class RendererLayout:
VALID_ORIENTATIONS = ['h', 'v']
def __init__(self, cfg: LayoutConfig, nplots: int):
self.cfg = cfg
self.nplots = nplots
# Setup layout
self.nrows, self.ncols = self._calc_layout()
self.orientation = cfg.orientation
if self.orientation not in self.VALID_ORIENTATIONS:
raise ValueError(f'Invalid orientation {self.orientation} not in '
f'{self.VALID_ORIENTATIONS}')
def _calc_layout(self):
"""
Inputs: self.cfg, self.waves
:return: (nrows, ncols)
"""
cfg = self.cfg
if cfg.nrows:
nrows = cfg.nrows
if nrows is None:
raise ValueError('invalid cfg: rows_first is True and nrows is None')
ncols = ceildiv(self.nplots, nrows)
else:
ncols = cfg.ncols
if ncols is None:
raise ValueError('invalid cfg: rows_first is False and ncols is None')
nrows = ceildiv(self.nplots, ncols)
return nrows, ncols
def arrange(self, region_factory: RegionFactory) -> List[Region]:
""" Generates an array of regions.
index, row, column are fed into region_factory in a row-major order [row][col].
The results are possibly reshaped into column-major order [col][row].
"""
nspaces = self.nrows * self.ncols
inds = np.arange(nspaces)
rows, cols = np.unravel_index(inds, (self.nrows, self.ncols))
row_col = list(zip(rows, cols))
regions = np.empty(len(row_col), dtype=object) # type: np.ndarray[Region]
regions[:] = [region_factory(*rc) for rc in row_col]
regions2d = regions.reshape((self.nrows, self.ncols)) # type: np.ndarray[Region]
# if column major:
if self.orientation == 'v':
regions2d = regions2d.T
return regions2d.flatten()[:self.nplots].tolist()

Wyświetl plik

@ -0,0 +1,77 @@
import pytest
from ovgenpy.layout import LayoutConfig, RendererLayout
from ovgenpy.renderer import RendererConfig, MatplotlibRenderer
from tests.test_renderer import WIDTH, HEIGHT
def test_layout_config():
with pytest.raises(ValueError):
LayoutConfig(nrows=1, ncols=1)
one_col = LayoutConfig(ncols=1)
assert one_col
one_row = LayoutConfig(nrows=1)
assert one_row
default = LayoutConfig()
assert default.ncols == 1 # Should default to single-column layout
assert default.nrows is None
assert default.orientation == 'h'
@pytest.mark.parametrize('lcfg', [
LayoutConfig(ncols=2),
LayoutConfig(nrows=8),
])
@pytest.mark.parametrize('region_type', [str, tuple, list])
def test_hlayout(lcfg, region_type):
nplots = 15
layout = RendererLayout(lcfg, nplots)
assert layout.ncols == 2
assert layout.nrows == 8
regions = layout.arrange(lambda row, col: region_type((row, col)))
assert len(regions) == nplots
assert regions[0] == region_type((0, 0))
assert regions[1] == region_type((0, 1))
assert regions[2] == region_type((1, 0))
m = nplots - 1
assert regions[m] == region_type((m // 2, m % 2))
@pytest.mark.parametrize('lcfg', [
LayoutConfig(ncols=3, orientation='v'),
LayoutConfig(nrows=3, orientation='v'),
])
@pytest.mark.parametrize('region_type', [str, tuple, list])
def test_vlayout(lcfg, region_type):
nplots = 7
layout = RendererLayout(lcfg, nplots)
assert layout.ncols == 3
assert layout.nrows == 3
regions = layout.arrange(lambda row, col: region_type((row, col)))
assert len(regions) == nplots
assert regions[0] == region_type((0, 0))
assert regions[2] == region_type((2, 0))
assert regions[3] == region_type((0, 1))
assert regions[6] == region_type((0, 2))
def test_renderer_layout():
# 2 columns
cfg = RendererConfig(WIDTH, HEIGHT)
lcfg = LayoutConfig(ncols=2)
nplots = 15
r = MatplotlibRenderer(cfg, lcfg, nplots)
# 2 columns, 8 rows
assert r.layout.ncols == 2
assert r.layout.nrows == 8

Wyświetl plik

@ -4,93 +4,12 @@ from matplotlib.colors import to_rgb
from ovgenpy.channel import ChannelConfig
from ovgenpy.outputs import RGB_DEPTH
from ovgenpy.renderer import RendererConfig, MatplotlibRenderer, LayoutConfig, \
RendererLayout
from ovgenpy.renderer import RendererConfig, MatplotlibRenderer
from ovgenpy.layout import LayoutConfig
WIDTH = 640
HEIGHT = 360
def test_config():
with pytest.raises(ValueError):
LayoutConfig(nrows=1, ncols=1)
one_col = LayoutConfig(ncols=1)
assert one_col
one_row = LayoutConfig(nrows=1)
assert one_row
default = LayoutConfig()
assert default.ncols == 1 # Should default to single-column layout
assert default.nrows is None
assert default.orientation == 'h'
@pytest.mark.parametrize('lcfg', [
LayoutConfig(ncols=2),
LayoutConfig(nrows=8),
])
@pytest.mark.parametrize('region_type', [str, tuple, list])
def test_hlayout(lcfg, region_type):
nplots = 15
layout = RendererLayout(lcfg, nplots)
assert layout.ncols == 2
assert layout.nrows == 8
regions = layout.arrange(lambda row, col: region_type((row, col)))
assert len(regions) == nplots
assert regions[0] == region_type((0, 0))
assert regions[1] == region_type((0, 1))
assert regions[2] == region_type((1, 0))
m = nplots - 1
assert regions[m] == region_type((m // 2, m % 2))
@pytest.mark.parametrize('lcfg', [
LayoutConfig(ncols=3, orientation='v'),
LayoutConfig(nrows=3, orientation='v'),
])
@pytest.mark.parametrize('region_type', [str, tuple, list])
def test_vlayout(lcfg, region_type):
nplots = 7
layout = RendererLayout(lcfg, nplots)
assert layout.ncols == 3
assert layout.nrows == 3
regions = layout.arrange(lambda row, col: region_type((row, col)))
assert len(regions) == nplots
assert regions[0] == region_type((0, 0))
assert regions[2] == region_type((2, 0))
assert regions[3] == region_type((0, 1))
assert regions[6] == region_type((0, 2))
def test_renderer():
"""
TODO check image output using:
https://matplotlib.org/devel/testing.html#writing-an-image-comparison-test
https://stackoverflow.com/a/27950953
"[I]mage comparison tests end up bring more trouble than they are worth"
"""
# 2 columns
cfg = RendererConfig(WIDTH, HEIGHT)
lcfg = LayoutConfig(ncols=2)
nplots = 15
r = MatplotlibRenderer(cfg, lcfg, nplots)
# 2 columns, 8 rows
assert r.layout.ncols == 2
assert r.layout.nrows == 8
ALL_ZEROS = np.array([0,0])
all_colors = pytest.mark.parametrize('bg_str,fg_str', [