kopia lustrzana https://github.com/corrscope/corrscope
Move LayoutConfig and RendererLayout to new file
rodzic
62e8c800f4
commit
342752febf
|
@ -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()
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
|
@ -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', [
|
||||
|
|
Ładowanie…
Reference in New Issue