ezmsg.sigproc.util.channels#

One way to say “split these channels into groups”.

Several sources attach a structured CoordinateAxis to the channel dimension carrying per-channel fields — e.g. ezmsg-blackrock’s ChannelMap emits a ch axis whose .data is a numpy struct array with x/y/size/label/array/bank/elec/headstage. Operations that treat channels in groups — per-bank rereferencing, block spatial filters — need to turn that metadata into index groups.

ChannelGroupSpec is the single spec type every such operation accepts, and resolve_channel_groups() is the single resolver:

spec

meaning

None

no grouping (caller’s default applies)

"bank"

group by that struct-array field

("array", "bank")

group by the tuple of those fields

[[0, 1, 2], [3, 4, 5]]

explicit index groups

fn(message, axis) -> groups

anything else

Groups are returned in first-appearance order along the channel axis so a resolved grouping is reproducible and readable against the channel table.

Module Attributes

ChannelGroupSpec

How to split a channel axis into groups.

Functions

channel_groups_from_field(message, axis=None, field='bank')[source]#

Group channel indices by one or more fields of a structured coordinate axis.

Parameters:
  • message (AxisArray) – Message whose axis coordinate is a structured CoordinateAxis (its .data is a structured numpy array).

  • axis (str | None) – Channel axis name. None defaults to the last dimension.

  • field (str | Sequence[str]) – Struct-array field to group by (e.g. "bank"), or a sequence of fields to group by their tuple (e.g. ("array", "bank")).

Returns:

Index groups, one per distinct value, in first-appearance order. None when the axis carries no usable structured field (no such axis, no .data, unstructured .data, a field is absent, or the per-channel length doesn’t match the data). Returning None rather than a single all-channel group lets callers distinguish “no metadata, fall back to my default” from “one bank”.

Return type:

list[ndarray] | None

group_spec_fields(spec)[source]#

The metadata field names spec groups by, or None if it needs none.

Explicit index groups, callables and None all return None: nothing about them can change with the message.

Deliberately O(1) — a field spec is discriminated by its first element, never by scanning all of them, because this runs on the per-message hash path. resolve_channel_groups() does the full homogeneity check once, at state reset, so a malformed spec still fails loudly.

Parameters:

spec (str | Sequence[str] | Sequence[Sequence[int]] | Callable[[AxisArray, str], Sequence[Sequence[int]] | None] | None)

Return type:

tuple[str, …] | None

group_spec_fingerprint(message, axis, spec)[source]#

O(1) summary of whether spec can resolve against this message.

Transformers fold this into their per-message state hash so a stream that gains or loses the grouping field re-resolves its groups, without paying to hash the field’s bytes on every message. Field values changing under a fixed key and channel count is deliberately not detected — a genuine remap arrives with a new key or channel count.

The two common specs – None and a single field name – are classified inline rather than through group_spec_fields(), because at this call rate the function call itself is a measurable share of the cost. Both shortcuts must agree with that function; everything less trivial defers to it.

Parameters:
Return type:

tuple

resolve_channel_groups(message, axis, spec)[source]#

Turn a ChannelGroupSpec into validated index groups.

Returns None when spec is None or when a metadata-derived spec finds nothing to group by — in both cases the caller applies its own default (typically “all channels in one group”).

Parameters:
Return type:

list[ndarray] | None

validate_channel_groups(groups, n_channels)[source]#

Raise ValueError unless groups are in-range and pairwise disjoint.

Disjointness matters because every consumer of a grouping assumes a channel belongs to at most one group — a channel listed twice would be rereferenced twice, or would have two weight blocks written to the same output. Not every channel has to appear: omitted channels are the caller’s business (rereferencing passes them through unchanged).

An empty groups list, or empty groups within it, validates trivially.

Parameters:
Return type:

None