Beispiel #1
0
def test_render_output():
    """ Ensure rendering to output does not raise exceptions. """
    datas = [RENDER_Y_ZEROS]

    renderer = Renderer(CFG.render, CFG.layout, datas, None, None)
    out: FFmpegOutput = NULL_FFMPEG_OUTPUT(CFG)

    renderer.update_main_lines(datas)
    out.write_frame(renderer.get_frame())

    assert out.close() == 0
Beispiel #2
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)
Beispiel #3
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
Beispiel #4
0
def test_renderer_knows_stride(mocker: "pytest_mock.MockFixture", integration: bool):
    """
    If Renderer draws both "main line" and "custom mono lines" at once,
    each line must have its x-coordinates multiplied by the stride.

    Renderer uses "main line stride = 1" by default,
    but this results in the main line appearing too narrow compared to debug lines.
    Make sure CorrScope.play() gives Renderer the correct values.
    """

    # Stub out FFplay output.
    mocker.patch.object(FFplayOutputConfig, "cls")

    subsampling = 2
    width_mul = 3

    chan_cfg = ChannelConfig("tests/sine440.wav", render_width=width_mul)
    corr_cfg = template_config(
        render_subsampling=subsampling, channels=[chan_cfg], end_time=0
    )

    if integration:
        corr = CorrScope(corr_cfg, Arguments(".", [FFplayOutputConfig()]))
        corr.play()
        assert corr.renderer.render_strides == [subsampling * width_mul]
    else:
        channel = Channel(chan_cfg, corr_cfg, channel_idx=0)
        data = channel.get_render_around(0)
        renderer = Renderer(
            corr_cfg.render, corr_cfg.layout, [data], [chan_cfg], [channel]
        )
        assert renderer.render_strides == [subsampling * width_mul]
Beispiel #5
0
 def _load_renderer(self) -> RendererFrontend:
     dummy_datas = [channel.get_render_around(0) for channel in self.channels]
     renderer = Renderer(
         self.cfg.render,
         self.cfg.layout,
         dummy_datas,
         self.cfg.channels,
         self.channels,
     )
     return renderer
Beispiel #6
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"
Beispiel #7
0
def test_frontend_overrides_backend(mocker: "pytest_mock.MockFixture"):
    """
    class Renderer inherits from (RendererFrontend, backend).

    RendererFrontend.get_frame() is a wrapper around backend.get_frame()
    and should override it (RendererFrontend should come first in MRO).

    Make sure RendererFrontend methods overshadow backend methods.
    """

    # If RendererFrontend.get_frame() override is removed, delete this entire test.
    frontend_get_frame = mocker.spy(RendererFrontend, "get_frame")
    backend_get_frame = mocker.spy(AbstractMatplotlibRenderer, "get_frame")

    corr_cfg = template_config()
    chan_cfg = ChannelConfig("tests/sine440.wav")
    channel = Channel(chan_cfg, corr_cfg, channel_idx=0)
    data = channel.get_render_around(0)

    renderer = Renderer(corr_cfg.render, corr_cfg.layout, [data], [chan_cfg], [channel])
    renderer.update_main_lines([data])
    renderer.get_frame()

    assert frontend_get_frame.call_count == 1
    assert backend_get_frame.call_count == 1
Beispiel #8
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
Beispiel #9
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)
Beispiel #10
0
def verify(r: Renderer, appear: Appearance, datas: List[Optional[np.ndarray]]):
    bg_str = appear.bg.color

    fg_str = appear.fg.color
    draw_fg = appear.fg.draw_fg
    fg_line_width = appear.fg.line_width

    grid_str = appear.grid.color
    grid_line_width = appear.grid.line_width

    viewport_width = appear.debug.viewport_width

    if draw_fg:
        r.update_main_lines(datas)

    frame_colors: np.ndarray = np.frombuffer(r.get_frame(), dtype=np.uint8).reshape(
        (-1, BYTES_PER_PIXEL)
    )

    bg_u8 = color_to_bytes(bg_str)
    all_colors = [bg_u8]

    fg_u8 = color_to_bytes(fg_str)
    if draw_fg:
        all_colors.append(fg_u8)

    is_grid = bool(grid_str and grid_line_width >= 1)

    if is_grid:
        grid_u8 = color_to_bytes(grid_str)
        all_colors.append(grid_u8)
    else:
        grid_u8 = np.array([1000] * BYTES_PER_PIXEL)

    data = datas[0]
    assert (data.shape[1] > 1) == (data is RENDER_Y_STEREO)
    is_stereo = is_grid and data.shape[1] > 1
    if is_stereo:
        stereo_grid_u8 = (grid_u8 * OPACITY + bg_u8 * (1 - OPACITY)).astype(int)
        all_colors.append(stereo_grid_u8)

    # Ensure background is correct
    bg_frame = frame_colors[0]
    assert (
        bg_frame == bg_u8
    ).all(), f"incorrect background, it might be grid_str={grid_str}"

    # Ensure foreground is present
    does_fg_appear_here = np.prod(frame_colors == fg_u8, axis=-1)
    does_fg_appear = does_fg_appear_here.any()
    # it might be 136 == #888888 == init_line_color
    assert does_fg_appear == draw_fg, f"{does_fg_appear} != {draw_fg}"

    if draw_fg:
        expected_fg_pixels = NPLOTS * (WIDTH / viewport_width) * fg_line_width
        assert does_fg_appear_here.sum() == pytest.approx(
            expected_fg_pixels, abs=expected_fg_pixels * 0.1
        )

    # Ensure grid color is present
    does_grid_appear_here = np.prod(frame_colors == grid_u8, axis=-1)
    does_grid_appear = does_grid_appear_here.any()
    assert does_grid_appear == is_grid, f"{does_grid_appear} != {is_grid}"

    if is_grid:
        assert np.sum(does_grid_appear_here) == pytest.approx(
            GRID_NPIXEL * grid_line_width, abs=GRID_NPIXEL * 0.1
        )

    # Ensure stereo grid color is present
    if is_stereo:
        assert (
            np.min(np.sum(np.abs(frame_colors - stereo_grid_u8), axis=-1)) < TOLERANCE
        ), "Missing stereo gridlines"

    assert (np.amax(frame_colors, axis=0) == np.amax(all_colors, axis=0)).all()
    assert (np.amin(frame_colors, axis=0) == np.amin(all_colors, axis=0)).all()