Skip to content
led-ticker

Tool: panel-map

This page is for builders wiring their own multi-panel sign who need a pixel_mapper_config Remap string — the line that tells the library which panel is first in the cable chain, where each one lands on the canvas, and how each is rotated. Hand-deriving it is the usual pain: edit → photograph random scrolling text → guess → repeat.

panel-map replaces that loop with a four-step workflow: reveal your layout on the panels → transcribe the grid you see → derive the string → verify it on the real canvas. It is implemented at scripts/panel_map.py and mirrors the conventions of panel-test.

Like panel-test, panel-map reads the [display] section of any led-ticker config TOML and reuses the same LedFrame construction the main app uses — so every panel knob (rows, cols, chain_length, panel_type, gpio_slowdown, rp1_pio) is exactly what the running ticker would see. Widget config is ignored.

All commands below use the -docker targets — the supported path on a deployed sign. They run inside the production image, so build it once first with make build. (A dev host with the repo and uv installed can drop the -docker suffix; see the subcommand reference.)

The bigsign is a 2×4 vertical-serpentine layout: eight P3 32×64 panels, data chain entering at the bottom-right, climbing each column bottom-to-top, then jumping diagonally to the bottom of the next column to the left. Here is the end-to-end walkthrough.

Stop the ticker and paint the calibration pattern:

Terminal window
docker compose stop
make panel-map-reveal-docker CONFIG=config/config.bigsign.example.toml

Each physical panel lights up with a number, an up-arrow, a bright corner dot in the logical top-left, a border around the slot, and an underline directly beneath the digit so a rotated number (a sideways 6 or 9) is never ambiguous — you read the digit relative to its underline, not relative to the room.

The bigsign panels under reveal: top row reads 1 3 5 7, bottom row 2 4 6 8, each with a yellow up-arrow, a corner dot, and an underline beneath the digit.

Shoot the wall square-on (lights toward you, camera level). Write one <number><flag> token per panel. Write the top wall row first, left to right, exactly as the panels hang — transcribe the numbers as you see them, even when they look out of order. Do not rotate the photo to make them count up. For the bigsign (8 panels, all installed upright) the wall above reads:

1n 3n 5n 7n
2n 4n 6n 8n

These numbers are reveal’s own labels, not your cable order — they run opposite to it (here reveal paints 1 on the panel farthest down the chain and 8 on the cable-entry panel). That’s fine, and it’s the whole point: you never have to reason about your wiring. Transcribe what you see and derive untangles the snake for you. Save it to a file:

Terminal window
cat > layout.txt <<'EOF'
1n 3n 5n 7n
2n 4n 6n 8n
EOF

Or just open layout.txt in any editor (nano layout.txt on the Pi) and type the grid.

For a single-row chain (e.g. 5 panels side by side), the grid is one line: 1n 2n 3n 4n 5n.

The n flag means each panel is oriented normally (arrow up, dot top-left). See the orientation legend below for rotated panels.

Terminal window
make panel-map-derive-docker LAYOUT=layout.txt CONFIG=config/config.bigsign.example.toml

derive prints the Remap string to stdout:

Remap:256,64|192,32n|192,0n|128,32n|128,0n|64,32n|64,0n|0,32n|0,0n

Copy that into your [display] block:

pixel_mapper_config = "Remap:256,64|192,32n|192,0n|128,32n|128,0n|64,32n|64,0n|0,32n|0,0n"

This string matches config/config.bigsign.example.toml character-for-character. A golden tripwire test keeps them in sync — if derive ever diverges, CI fails.

Paste the string into your config (or supply it directly with --mapper) and confirm the canvas is coherent:

Terminal window
make panel-map-verify-docker CONFIG=config/config.bigsign.example.toml
# or supply the string directly:
make panel-map-verify-docker MAPPER="Remap:256,64|192,32n|192,0n|128,32n|128,0n|64,32n|64,0n|0,32n|0,0n"
docker compose start

verify paints a panel-seam grid and, in every cell, a reading-order number (1, 2, 3, 4 across the top, 5, 6, 7, 8 across the bottom — left to right, top to bottom), an up-arrow, and a red dot in the top-left corner. A correct mapper makes those numbers read in order across the whole wall, every arrow pointing up, every dot top-left:

The bigsign panels under verify with the correct mapper: numbers read 1 2 3 4 across the top and 5 6 7 8 across the bottom, every arrow up, every corner dot top-left.

A wrong mapper scrambles those numbers (a panel lands out of sequence) or rotates an arrow (a panel’s orientation flag is off) — each panel diagnoses itself. See How to read verify below.

reveal — chain and orientation calibration

Section titled “reveal — chain and orientation calibration”

Runs with pixel_mapper_config forced to the identity mapping (no panel rearrangement), so each panel shows the number of the raw frame-buffer slot that physically lands on it. Those numbers are the tool’s own labels — you transcribe them and derive converts. (They run opposite to cable order; you never need to match them to your wiring.) Photograph the result and you have everything derive needs.

Terminal window
docker compose stop
make panel-map-reveal-docker
make panel-map-reveal-docker CONFIG=config/config.mysign.toml HOLD=5
docker compose start

On a dev host with the repo and uv installed (not a deployed sign), drop the -docker suffix: make panel-map-reveal. The make targets wrap python scripts/panel_map.py reveal --config $(CONFIG) --hold $(HOLD) — the same CONFIG= / HOLD= override convention as make panel-test.

Per panel, reveal paints (all via SetPixel, never DrawText):

  • The panel number as a large BDF digit, centered.
  • An up-arrow — the primary rotation tell.
  • A bright dot in the logical top-left corner — disambiguates 90° vs 270°.
  • A 1 px border around the slot.
  • A short underline directly beneath the digit.

Ctrl-C exits cleanly: a final black frame is swapped on so the panel never sticks on the calibration pattern.

FlagTypeDefaultMeaning
--configpathconfig/config.tomlConfig TOML to read [display] from.
--holdfloat2.0Seconds between swap ticks (extend for a steady hold).

derive — string generator (no hardware needed)

Section titled “derive — string generator (no hardware needed)”

Reads the transcribed grid from --layout FILE and prints the Remap: string to stdout. Diagnostics (chain-count mismatch warnings, parse errors) go to stderr. No hardware is needed — this step is pure computation.

Terminal window
make panel-map-derive-docker LAYOUT=layout.txt
make panel-map-derive-docker LAYOUT=layout.txt CONFIG=config/config.mysign.toml

The -docker target pipes your layout.txt into the image’s stdin; on a dev host with uv, make panel-map-derive LAYOUT=layout.txt runs it directly. The CONFIG argument is read only for chain_length × parallel, as a correctness check on the cell count in your grid — a mismatch warns on stderr but doesn’t hard-fail.

FlagTypeDefaultMeaning
--configpathconfig/config.tomlConfig TOML — read for chain_length / parallel correctness check only.
--layoutpathstdinLayout file (recommended). One row per wall row, cells <number><flag>.

Validation messages are plain-language — for example:

  • "You listed panel 1 twice. Each panel number must appear exactly once in your grid."
  • "Row 2 has 3 panels but row 1 has 4. Every wall row needs the same number of cells."
  • "'7z' isn't a valid cell. Each cell is a panel number followed by one of n, s, e, w, x (e.g. '3s')."

derive supports a single data chain (parallel = 1), which covers every reference build. A multi-chain (parallel > 1) config is refused with a clear message rather than emitting a silently-wrong string.

verify — coherent full-canvas diagnostic

Section titled “verify — coherent full-canvas diagnostic”

Applies a candidate mapper and paints a self-diagnosing image across the whole finished canvas. A correct mapper produces a coherent, in-order image; a wrong one shows exactly which panel is off and how.

Terminal window
docker compose stop
make panel-map-verify-docker CONFIG=config/config.mysign.toml
make panel-map-verify-docker MAPPER="Remap:256,64|..."
docker compose start

On a dev host, drop the -docker suffix. The painted image is independent of the mapper string — it’s the same reference picture every time; the diagnostic lives entirely in how the hardware then routes it onto your panels. In each visible cell verify paints:

  • A panel-seam grid: faint lines every cols / rows pixels — seams should fall exactly on physical panel edges.
  • A reading-order number (1, 2, 3 … left to right, top to bottom) — a wrong mapper lands these out of sequence.
  • An up-arrow and a red top-left corner dot — a wrong orientation flag rotates them.
FlagTypeDefaultMeaning
--configpathconfig/config.tomlConfig TOML. Uses pixel_mapper_config from this file unless --mapper overrides.
--holdfloat2.0Seconds between swap ticks.
--mapperstringOverride the mapper string (e.g. paste derive’s output directly without editing the TOML).

verify paints reading-order numbers onto the logical canvas — the single flat image the software draws — and the hardware mapper then rearranges that image onto your physical panels. So a correct mapper makes the wall read in order; a wrong mapper’s mistakes show up per panel:

  • A panel shows a number that’s out of sequence (e.g. a 5 where you expect 2) → that panel’s x,y position in the Remap string is wrong (a chain-order error). Fix: correct that panel’s position in layout.txt and re-run derive.
  • A panel’s number or arrow looks rotated → that panel’s orientation flag is wrong. Fix: change its flag. For e/w mix-ups the fix is a single-character swap — see Flip-and-retry.
  • The whole wall is scrambled, nothing in order → most likely either the Remap header size (Remap:WIDTH,HEIGHT|…) doesn’t match your real canvas, or the reveal photo was transcribed rotated/mirrored. Re-shoot square-on and start again from reveal; don’t tweak individual cells.

For a single-panel fix, identify the panel by its number in the overlay, correct that one entry, and run verify again — you rarely need to re-transcribe the whole layout.

What you observe on a physical panel after running reveal → the flag to write in your layout grid. The arrow points toward the flag letter: east = right, west = left.

What you see on that physical panelFlagRotation
Arrow up, dot top-leftn0° (normal)
Arrow down, dot bottom-rights180°
Arrow right, dot top-righte270°
Arrow left, dot bottom-leftw90°
(panel not used in the display)xdiscard

If a rotated panel still looks wrong after verify, the most likely cause is an e/w swap. Fix it in about 10 seconds:

  1. Identify the panel by its number in the verify overlay.
  2. Find that cell in layout.txt.
  3. Change ew or we.
  4. Re-run make panel-map-derive-docker LAYOUT=layout.txt to get the new string.
  5. Re-run make panel-map-verify-docker MAPPER="..." to confirm.

You don’t need to re-transcribe the whole layout for one orientation fix.

Stand in front of the wall. Write one <number><flag> token per panel — the top wall row first, left to right, exactly as the panels hang. A panel’s row and column in the grid are determined by where it sits on the wall, not by its cable position or rotation. derive re-sorts into chain order internally, so you never need to think in chain order — you only describe what you see.

Shoot the wall square-on: lights toward you, camera level, no angle or tilt. A tilted or angled shot can mirror left/right or ambiguate top/bottom, introducing transcription errors before you even open a text file.

Numbers ≥ 10 rendered sideways (on a rotated panel) can be hard to read. For the reference builds (up to 8 panels) this is not an issue. For larger walls with 10 or more panels, allow extra time reading the reveal photo, or label the physical panels before shooting.

derive computes uniform-cell rectangular grids: every wall row has the same number of panels, and every panel occupies the same cols × rows footprint on the canvas. It handles a single data chain (parallel = 1) — both reference builds, and the vast majority of hobbyist walls, are single-chain.

  • n, s, e, and w panels are all supported. n/s (0°/180°) keep a panel’s footprint; e/w (90°/270°) swap its width and height.
  • A wall that mixes rotated and un-rotated panels may be physically non-rectangular (rows of unequal pixel height). derive assumes a uniform grid, so for a mixed-rotation wall, use verify to check every panel and hand-edit any e/w entry the flip-and-retry loop doesn’t settle.
  • parallel > 1 (multiple electrical chains) is refused by derive with a clear message — hand-write the Remap string for that layout.

reveal and verify are hardware tools: they drive the matrix and so need --privileged in Docker (the supported path) — the same constraint as panel-test. The ticker and these tools cannot share the matrix; stop the ticker (docker compose stop) before running either. derive needs no hardware.