Skip to content

Repository files navigation

display-patterns

Deterministic display test patterns, device-free and exact.

Test-pattern math tends to live trapped inside device tools. This library is the importable version: solid fields, checkerboards, charts, and machine-readable temporal-alignment patterns, rendered as pure functions of their parameters and a frame index. Integer patterns deliver exact code values at a stated bit depth — the property measurement work depends on — and the render path never touches a device, a clock, or a file.

The core depends on numpy alone and renders into a caller-supplied array namespace (numpy or torch, CPU or GPU), so it serves CLI signal generators and GPU-resident render graphs alike. Render bodies are functional and free of Python-level branching on frame data, so a consumer can compile them into its own graph. Chart authoring and TIFF export install as extras.

Used by bmd-signal-gen, which sends the rendered patterns to a display over SDI or HDMI. Related: display-measure, display-report, and methodology, which measure, report on, and document the characterization of a display surface.

Installation

pip install display-patterns              # core: numpy only
pip install "display-patterns[charts,io]" # chart authoring + TIFF export

Until the first PyPI release, install from a checkout: pip install . (or uv pip install .).

Usage

Render a 12-bit checkerboard at exact code values. Every catalog entry is a pure function of its parameters and a frame index (stills ignore the index), rendered through a caller-supplied array namespace — numpy by default, torch on GPU hosts:

from display_patterns import checkerboard

frame = checkerboard(
    [[4095, 2048, 0], [0, 0, 0]], width=1920, height=1080, bit_depth=12
)  # uint16 (1080, 1920, 3)

# GPU-resident render, no host round trip:
# frame = checkerboard(..., xp=torch, device="cuda")

dtype states the output type, uint16 by default. Any type that carries the stated bit depth exactly is accepted; one that cannot is refused rather than silently wrapping. See Backends for when to state it.

Encode a frame counter as machine-readable bit-cells and decode it back — render one end of a video chain, measure latency and frame skew at the other:

from display_patterns import PanelGeometry, decode_counter, render_counter_panel

geometry = PanelGeometry.for_frame(width=1920, height=1080, bits=16)
overlay, mask = render_counter_panel(1234, geometry)  # float32 [0, 1], HWC
assert decode_counter(overlay, geometry) == 1234

The counter carries 1 to 31 bits and wraps at 2**bits — 31 bits runs over a year at 60 Hz. Pass the frame index as a zero-dimensional array rather than a Python integer when compiling (see below).

Backends

Every core pattern renders through the xp namespace with an optional device, and returns the same values on each. Two things are worth knowing before putting one inside a compiled graph.

Pass the frame index as array data. A Python integer is a compile-time constant, so stepping one recompiles the pattern on every frame — and past the recompile limit torch.compile gives up on the call site and falls back to eager for good:

counter = torch.tensor(frame_number, dtype=torch.int32, device=device)
overlay, mask = render_counter_panel(counter, geometry, xp=torch, device=device)

State a dtype your backend can compile. uint16 renders correctly on MPS in eager mode, but Inductor's Metal code generator has no mapping for it, so a uint16 output cannot be compiled there at all. An int32 or float32 output can, and carries 12-bit code values just as exactly:

frame = checkerboard(colors, ..., xp=torch, device=device, dtype=torch.int32)

Which types a backend supports is the backend's business and moves between its releases, which is why the library takes the dtype from the caller rather than fixing one.

Verified on numpy and on torch's CPU build in CI; on CUDA and MPS by pytest -m cuda and pytest -m mps, which are deselected by default and run where the hardware is.

With the charts and io extras, author a chart in YAML, render it, and write a 16-bit TIFF:

from display_patterns.charts import render_chart, write_chart_tiff
from display_patterns.charts.loaders import load_chart

layout = load_chart("my_chart.yaml")
image = render_chart(layout, bit_depth=12)
write_chart_tiff("my_chart.tiff", image, layout)

API

  • display_patterns / display_patterns.patterns — the core catalog: checkerboard (one color renders a solid), ROI, ColorRangeError, and the temporal-alignment counter panel (PanelGeometry, render_counter_panel, decode_counter). The root is the canonical import surface; no dependency beyond numpy — torch renders through the xp parameter without ever being required.
  • display_patterns.image_generators — legacy class surface (PatternGenerator), kept for existing consumers; delegates to the catalog.
  • display_patterns.charts — chart types, colorimetric conversion, and rendering (charts extra); TIFF read/write (io extra).
  • display_patterns.charts.loaders — YAML chart definitions (charts extra).

All public functions and classes carry NumPy-style docstrings; the package ships py.typed for static type checking.

Governance

License

BSD 3-Clause.

About

Deterministic display test patterns, device-free and exact.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages