Skip to content

Derive a grid layout from the ch axis - #10

Merged
cboulay merged 4 commits into
mainfrom
feat/channel-layout
Aug 7, 2026
Merged

Derive a grid layout from the ch axis#10
cboulay merged 4 commits into
mainfrom
feat/channel-layout

Conversation

@cboulay

@cboulay cboulay commented Aug 7, 2026

Copy link
Copy Markdown
Member

The geometry counterpart to chmeta, which already does this for names: given a
ch coordinate axis, work out where each channel's cell goes and how big it is.

It comes from intent-tools, where it fed an evoked-potential grid, and it
deliberately did not move into phosphor along with the grids it serves.
Reading x/y/size/headstage off a structured axis is decoding one
acquisition system's convention, and a renderer that takes plain arrays should
not have to know it. This is the layer that already owns that translation —
phosphor 0.9.x gained ChannelGridWidget and TraceGridWidget, both of which
take positions and sizes as arrays.

Labels delegate to chmeta.channel_names rather than repeating its fallback, so
a grid can name channels by bank and elec exactly as the sweep does. The
intent-tools version could only read a label field.

Everything degrades to a square-ish tiling: no ch axis, no coordinate fields,
or only one of the two. A plot that draws nothing is less useful than one that
draws the right number of cells in the wrong places, and geometry is missing
often enough that this is the common path, not the edge case.

ChannelLayoutCache

A grid deriving its layout from every message pays for it on every message,
while the answer changes about once a session. The cache recomputes only when
the axis actually differs — 102 us down to 6 us at 128 channels.

Fingerprinted on the axis' bytes rather than its identity: it arrives
deserialized from another process, so it is a new object every message
describing the same electrodes, and keying on identity would miss exactly where
the cache is wanted.

A cache rather than a shared instance, so two consumers watching one stream each
keep their own and neither has to know the other exists. The derivation stays
here, where any application can call it.

Notes

Exported lazily, like ShmemSweepWidget: it needs phosphor's geometry helpers,
and importing ezmsg.tools.plot must not pull in a GPU stack.

phosphor>=0.9.1 in the viewer and sigmon extras.

19 new tests.

🤖 Generated with Claude Code

cboulay added 4 commits August 6, 2026 12:19
The geometry counterpart to chmeta, which already does this for names: given a
ch coordinate axis, work out where each channel's cell goes and how big it is.

It comes from intent-tools, and deliberately did not move into phosphor with
the grids it feeds. Reading x/y/size/headstage off a structured axis is
decoding one acquisition system's convention, and a renderer that takes plain
arrays should not have to know it. This is the layer that already owns that
translation.

Labels delegate to chmeta.channel_names rather than repeating its fallback, so
a grid can name channels by bank and elec exactly as the sweep does -- the
intent-tools version could only read a label field.

Everything degrades to a square-ish tiling: no ch axis, no coordinate fields,
or only one of the two. A plot that draws nothing is less useful than one that
draws the right number of cells in the wrong places, and geometry is missing
often enough that this is the common path, not the edge case.

Exported lazily, like ShmemSweepWidget: it needs phosphor's geometry helpers,
and importing ezmsg.tools.plot must not pull in a GPU stack. 12 tests.
A grid deriving its layout from every message pays for it on every message,
while the answer changes about once a session. ChannelLayoutCache wraps
channel_layout and recomputes only when the axis actually differs: 102 us down
to 6 us at 128 channels.

Fingerprinted on the axis' bytes rather than its identity. It arrives
deserialized from another process, so it is a new object every message
describing the same electrodes -- keying on identity would make the cache miss
exactly where it is wanted.

A cache rather than a shared instance, so two consumers watching one stream each
keep their own and neither has to know the other exists. The derivation stays
here, where any application can call it.
CI installs the test group, not the viewer or sigmon extras, so phosphor is
absent -- which is the point of it being optional, and which these tests did not
allow for. Nine jobs failed on ModuleNotFoundError.

Guarded the way test_shmem_sweep already guards for the same reason, but keyed
on phosphor itself rather than the module under test: layout.py imports it
inside the function, so the module imports cleanly without it and only fails
when called. Keying on the module would never have skipped.

19 pass where phosphor is installed, 1 skipped where it is not.
@cboulay
cboulay merged commit b78a71f into main Aug 7, 2026
11 checks passed
@cboulay
cboulay deleted the feat/channel-layout branch August 7, 2026 01:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant