Esempio n. 1
0
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"
Esempio n. 2
0
def verify_res_divisor_rounding(
    target_int: int,
    res_divisor: float,
    speed_hack: bool,
):
    """Ensure that pathological-case float rounding errors
    don't cause inconsistent dimensions and assertion errors."""
    target_dim = target_int + 0.5
    undivided_dim = round(target_dim * res_divisor)

    cfg = RendererConfig(undivided_dim, undivided_dim, res_divisor=res_divisor)
    cfg.before_preview()

    with ExitStack() as stack:
        if speed_hack:
            stack.enter_context(
                patch.object(AbstractMatplotlibRenderer, "_save_background")
            )
            datas = []
        else:
            datas = [RENDER_Y_ZEROS]

        try:
            renderer = Renderer(cfg, LayoutConfig(), datas, None, None)
            if not speed_hack:
                renderer.update_main_lines(datas)
                renderer.get_frame()
        except Exception:
            perr(cfg.divided_width)
            raise
Esempio n. 3
0
def template_config(**kwargs) -> Config:
    """Default template values do NOT indicate optional attributes."""
    cfg = Config(
        master_audio="",
        fps=_FPS,
        amplification=1,
        trigger_ms=40,
        render_ms=40,
        trigger_subsampling=1,
        render_subsampling=2,
        trigger=CorrelationTriggerConfig(
            mean_responsiveness=0.0,
            edge_strength=1.0,
            reset_below=0.3,
            responsiveness=0.5,
            pitch_tracking=SpectrumConfig(),
            # post_trigger=ZeroCrossingTriggerConfig(),
        ),
        channels=[],
        layout=LayoutConfig(orientation="v", ncols=1),
        render=RendererConfig(
            1280,
            720,
            res_divisor=4 / 3,
            grid_color="#55aaff",
            v_midline=True,
            h_midline=True,
        ),
    )
    return attr.evolve(cfg, **kwargs)
Esempio n. 4
0
def default_config(**kwargs) -> Config:
    """ Default template values do NOT indicate optional attributes. """
    cfg = Config(
        render_subfps=1,
        master_audio="",
        fps=_FPS,
        amplification=1,
        trigger_ms=40,
        render_ms=40,
        trigger_subsampling=1,
        render_subsampling=2,
        trigger=CorrelationTriggerConfig(
            edge_strength=2,
            responsiveness=0.5,
            buffer_falloff=0.5,
            use_edge_trigger=False,
            # Removed due to speed hit.
            # post=LocalPostTriggerConfig(strength=0.1),
        ),
        channels=[],
        layout=LayoutConfig(orientation="v", ncols=1),
        render=RendererConfig(
            1280,
            720,
            res_divisor=4 / 3,
            midline_color="#404040",
            v_midline=True,
            h_midline=True,
        ),
    )
    return attr.evolve(cfg, **kwargs)
Esempio n. 5
0
def test_label_render(label_position: LabelPosition, data, hide_lines):
    """Test that text labels are drawn:
    - in the correct quadrant
    - with the correct color (defaults to init_line_color)
    - even if no lines are drawn at all
    """
    font_str = "#FF00FF"
    font_u8 = color_to_bytes(font_str)

    # If hide_lines: set line color to purple, draw text using the line color.
    # Otherwise: draw lines white, draw text purple,
    cfg_kwargs = {}
    if hide_lines:
        cfg_kwargs.update(init_line_color=font_str)

    cfg = RendererConfig(
        WIDTH,
        HEIGHT,
        antialiasing=False,
        label_font=Font(size=16, bold=True),
        label_position=label_position,
        label_color_override=font_str,
        **cfg_kwargs,
    )

    lcfg = LayoutConfig()

    nplots = 1
    labels = ["#"] * nplots
    datas = [data] * nplots

    r = Renderer(cfg, lcfg, datas, None, None)
    r.add_labels(labels)
    if not hide_lines:
        r.update_main_lines(datas)

    frame_buffer: np.ndarray = np.frombuffer(r.get_frame(), dtype=np.uint8).reshape(
        (r.h, r.w, BYTES_PER_PIXEL)
    )
    # Allow mutation
    frame_buffer = frame_buffer.copy()

    yslice = label_position.y.match(
        top=slice(None, r.h // 2), bottom=slice(r.h // 2, None)
    )
    xslice = label_position.x.match(
        left=slice(None, r.w // 2), right=slice(r.w // 2, None)
    )
    quadrant = frame_buffer[yslice, xslice]

    assert np.prod(quadrant == font_u8, axis=-1).any(), "Missing text"

    quadrant[:] = 0
    assert not np.prod(
        frame_buffer == font_u8, axis=-1
    ).any(), "Text appeared in wrong area of screen"
Esempio n. 6
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
Esempio n. 7
0
def test_line_colors(appear: Appearance, data):
    """Test channel-specific line color overrides"""
    cfg = get_renderer_config(appear)
    lcfg = LayoutConfig(orientation=ORIENTATION)
    datas = [data] * NPLOTS

    # Move line color (appear.fg.color) from renderer cfg to individual channel.
    chan = ChannelConfig(wav_path="", line_color=appear.fg.color)
    channels = [chan] * NPLOTS
    cfg.init_line_color = "#888888"
    chan.line_color = appear.fg.color

    r = Renderer(cfg, lcfg, datas, channels, None)
    verify(r, appear, datas)
Esempio n. 8
0
def test_default_colors(appear: Appearance, data):
    """Test the default background/foreground colors."""
    cfg = get_renderer_config(appear)
    lcfg = LayoutConfig(orientation=ORIENTATION)
    datas = [data] * NPLOTS

    r = Renderer(cfg, lcfg, datas, None, None)
    verify(r, appear, datas)

    # Ensure default ChannelConfig(line_color=None) does not override line color
    chan = ChannelConfig(wav_path="")
    channels = [chan] * NPLOTS
    r = Renderer(cfg, lcfg, datas, channels, None)
    verify(r, appear, datas)
Esempio n. 9
0
def test_renderer_layout():
    # 2 columns
    cfg = RendererConfig(WIDTH, HEIGHT)
    lcfg = LayoutConfig(ncols=2)
    nplots = 15

    datas = [RENDER_Y_ZEROS] * nplots
    r = Renderer(cfg, lcfg, datas, None, None)
    r.update_main_lines(RenderInput.wrap_datas(datas), [0] * nplots)
    layout = r.layout

    # 2 columns, 8 rows
    assert layout.wave_ncol == 2
    assert layout.wave_nrow == 8
Esempio n. 10
0
def test_line_colors(bg_str, fg_str, grid_str, data):
    """ Test channel-specific line color overrides """
    cfg = RendererConfig(
        WIDTH,
        HEIGHT,
        bg_color=bg_str,
        init_line_color="#888888",
        grid_color=grid_str,
        stereo_grid_opacity=OPACITY,
        line_width=2.0,
        antialiasing=False,
    )
    lcfg = LayoutConfig()

    chan = ChannelConfig(wav_path="", line_color=fg_str)
    channels = [chan] * NPLOTS
    r = MatplotlibRenderer(cfg, lcfg, NPLOTS, channels)
    verify(r, bg_str, fg_str, grid_str, data)
Esempio n. 11
0
def test_default_colors(bg_str, fg_str, grid_str, data):
    """ Test the default background/foreground colors. """
    cfg = RendererConfig(
        WIDTH,
        HEIGHT,
        bg_color=bg_str,
        init_line_color=fg_str,
        grid_color=grid_str,
        stereo_grid_opacity=OPACITY,
        line_width=2.0,
        antialiasing=False,
    )
    lcfg = LayoutConfig()

    r = MatplotlibRenderer(cfg, lcfg, NPLOTS, None)
    verify(r, bg_str, fg_str, grid_str, data)

    # Ensure default ChannelConfig(line_color=None) does not override line color
    chan = ChannelConfig(wav_path="")
    channels = [chan] * NPLOTS
    r = MatplotlibRenderer(cfg, lcfg, NPLOTS, channels)
    verify(r, bg_str, fg_str, grid_str, data)
Esempio n. 12
0
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)
Esempio n. 13
0
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))