kopia lustrzana https://github.com/corrscope/corrscope
231 wiersze
7.7 KiB
Python
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
|