corrscope/tests/test_layout.py

231 wiersze
7.7 KiB
Python

from typing import List
import hypothesis.strategies as hs
import numpy as np
import numpy.testing as npt
import pytest
from hypothesis import given, settings
from corrscope.layout import (
LayoutConfig,
RendererLayout,
RegionSpec,
Edges,
Orientation,
StereoOrientation,
)
from corrscope.renderer import RendererConfig, MatplotlibRenderer
from corrscope.util import ceildiv
from tests.test_renderer import WIDTH, HEIGHT, RENDER_Y_ZEROS
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"
# Small range to ensure many collisions.
# max_value = 3 to allow for edge-free space in center.
integers = hs.integers(-1, 3)
@given(nrows=integers, ncols=integers, row=integers, col=integers)
@settings(max_examples=500)
def test_edges(nrows: int, ncols: int, row: int, col: int):
if not (nrows > 0 and ncols > 0 and 0 <= row < nrows and 0 <= col < ncols):
with pytest.raises(ValueError):
edges = Edges.at(nrows, ncols, row, col)
return
edges = Edges.at(nrows, ncols, row, col)
assert bool(edges & Edges.Left) == (col == 0)
assert bool(edges & Edges.Right) == (col == ncols - 1)
assert bool(edges & Edges.Top) == (row == 0)
assert bool(edges & Edges.Bottom) == (row == nrows - 1)
@pytest.mark.parametrize("lcfg", [LayoutConfig(ncols=2), LayoutConfig(nrows=8)])
def test_hlayout(lcfg):
nplots = 15
layout = RendererLayout(lcfg, [1] * nplots)
assert layout.wave_ncol == 2
assert layout.wave_nrow == 8
region2d: List[List[RegionSpec]] = layout.arrange(lambda arg: arg)
assert len(region2d) == nplots
for i, regions in enumerate(region2d):
assert len(regions) == 1, (i, len(regions))
np.testing.assert_equal(region2d[0][0].pos, (0, 0))
np.testing.assert_equal(region2d[1][0].pos, (0, 1))
np.testing.assert_equal(region2d[2][0].pos, (1, 0))
m = nplots - 1
npt.assert_equal(region2d[m][0].pos, (m // 2, m % 2))
@pytest.mark.parametrize(
"lcfg",
[LayoutConfig(ncols=3, orientation="v"), LayoutConfig(nrows=3, orientation="v")],
)
def test_vlayout(lcfg):
nplots = 7
layout = RendererLayout(lcfg, [1] * nplots)
assert layout.wave_ncol == 3
assert layout.wave_nrow == 3
region2d: List[List[RegionSpec]] = layout.arrange(lambda arg: arg)
assert len(region2d) == nplots
for i, regions in enumerate(region2d):
assert len(regions) == 1, (i, len(regions))
np.testing.assert_equal(region2d[0][0].pos, (0, 0))
np.testing.assert_equal(region2d[2][0].pos, (2, 0))
np.testing.assert_equal(region2d[3][0].pos, (0, 1))
np.testing.assert_equal(region2d[6][0].pos, (0, 2))
@given(
wave_nchans=hs.lists(hs.integers(1, 10), min_size=1, max_size=100),
orientation=hs.sampled_from(Orientation),
stereo_orientation=hs.sampled_from(StereoOrientation),
nrow_ncol=hs.integers(1, 100),
is_nrows=hs.booleans(),
)
def test_stereo_layout(
orientation: Orientation,
stereo_orientation: StereoOrientation,
wave_nchans: List[int],
nrow_ncol: int,
is_nrows: bool,
):
"""
Not-entirely-rigorous test for layout computation.
Mind-numbingly boring to write (and read?).
Honestly I prefer a good naming scheme in RendererLayout.arrange()
over unit tests.
- This is a regression test...
- And an obstacle to refactoring or feature development.
"""
# region Setup
if is_nrows:
nrows = nrow_ncol
ncols = None
else:
nrows = None
ncols = nrow_ncol
lcfg = LayoutConfig(
orientation=orientation,
nrows=nrows,
ncols=ncols,
stereo_orientation=stereo_orientation,
)
nwaves = len(wave_nchans)
layout = RendererLayout(lcfg, wave_nchans)
# endregion
# Assert layout dimensions correct
assert layout.wave_ncol == ncols or ceildiv(nwaves, nrows)
assert layout.wave_nrow == nrows or ceildiv(nwaves, ncols)
region2d: List[List[RegionSpec]] = layout.arrange(lambda r_spec: r_spec)
# Loop through layout regions
assert len(region2d) == len(wave_nchans)
for wave_i, wave_chans in enumerate(region2d):
stereo_nchan = wave_nchans[wave_i]
assert len(wave_chans) == stereo_nchan
# Compute channel dims within wave.
if stereo_orientation == StereoOrientation.overlay:
chans_per_wave = [1, 1]
elif stereo_orientation == StereoOrientation.v: # pos[0]++
chans_per_wave = [stereo_nchan, 1]
else:
assert stereo_orientation == StereoOrientation.h # pos[1]++
chans_per_wave = [1, stereo_nchan]
# Sanity-check position of channel 0 relative to origin (wave grid).
assert (np.add.reduce(wave_chans[0].pos) != 0) == (wave_i != 0)
npt.assert_equal(wave_chans[0].pos % chans_per_wave, 0)
for chan_j, chan in enumerate(wave_chans):
# Assert 0 <= position < size.
assert chan.pos.shape == chan.size.shape == (2,)
assert (0 <= chan.pos).all()
assert (chan.pos < chan.size).all()
# Sanity-check position of chan relative to origin (wave grid).
npt.assert_equal(
chan.pos // chans_per_wave, wave_chans[0].pos // chans_per_wave
)
# Check position of region (relative to channel 0)
chan_wave_pos = chan.pos - wave_chans[0].pos
if stereo_orientation == StereoOrientation.overlay:
npt.assert_equal(chan_wave_pos, [0, 0])
elif stereo_orientation == StereoOrientation.v: # pos[0]++
npt.assert_equal(chan_wave_pos, [chan_j, 0])
else:
assert stereo_orientation == StereoOrientation.h # pos[1]++
npt.assert_equal(chan_wave_pos, [0, chan_j])
# Check screen edges
screen_edges = chan.screen_edges
assert bool(screen_edges & Edges.Top) == (chan.row == 0)
assert bool(screen_edges & Edges.Left) == (chan.col == 0)
assert bool(screen_edges & Edges.Bottom) == (chan.row == chan.nrow - 1)
assert bool(screen_edges & Edges.Right) == (chan.col == chan.ncol - 1)
# Check stereo edges
wave_edges = chan.wave_edges
if stereo_orientation == StereoOrientation.overlay:
assert wave_edges == ~Edges.NONE
elif stereo_orientation == StereoOrientation.v: # pos[0]++
lr = Edges.Left | Edges.Right
assert wave_edges & lr == lr
assert bool(wave_edges & Edges.Top) == (chan.row % stereo_nchan == 0)
assert bool(wave_edges & Edges.Bottom) == (
(chan.row + 1) % stereo_nchan == 0
)
else:
assert stereo_orientation == StereoOrientation.h # pos[1]++
tb = Edges.Top | Edges.Bottom
assert wave_edges & tb == tb
assert bool(wave_edges & Edges.Left) == (chan.col % stereo_nchan == 0)
assert bool(wave_edges & Edges.Right) == (
(chan.col + 1) % stereo_nchan == 0
)
def test_renderer_layout():
# 2 columns
cfg = RendererConfig(WIDTH, HEIGHT)
lcfg = LayoutConfig(ncols=2)
nplots = 15
r = MatplotlibRenderer(cfg, lcfg, nplots, None)
r.render_frame([RENDER_Y_ZEROS] * nplots)
layout = r.layout
# 2 columns, 8 rows
assert layout.wave_ncol == 2
assert layout.wave_nrow == 8