Skip to content

channel cluster from field helper, car cluster by field - #162

Merged
cboulay merged 3 commits into
devfrom
car-bank-aware
Jul 2, 2026
Merged

channel cluster from field helper, car cluster by field#162
cboulay merged 3 commits into
devfrom
car-bank-aware

Conversation

@kylmcgr

@kylmcgr kylmcgr commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a way to derive per-cluster rereferencing groups from a structured field
of the channel coordinate axis, so CommonRereference (CAR) can rereference
within electrode banks without the caller hand-specifying index lists.

  • New helper ezmsg.sigproc.util.channels.channel_clusters_from_field(message, axis, field="bank")
    — groups channel indices by a structured-axis field, returns list[list[int]]
    or None when the axis carries no usable field (so callers can fall back).
  • CommonRereference gains a cluster_by_field setting. Precedence:
    explicit channel_clusters > cluster_by_field derivation > single
    all-channel cluster. Default None preserves current behavior.
  • _hash_message now folds the field's bytes into the state hash on the
    cluster_by_field path, so cached clusters re-derive if the field's values
    change while key/channel-count don't (scoped so the common case pays nothing).

This keys off a structured field, not channel-name strings, so it's
independent of channel naming.

Tests

test_util_channels.py (helper: grouping, first-appearance order, and every
None fallback) + bank-aware CAR cases in test_affine_transform.py. 54 pass.

@kylmcgr
kylmcgr requested a review from cboulay June 30, 2026 19:22
@kylmcgr kylmcgr self-assigned this Jun 30, 2026
_hash_message runs on every message. Hashing the whole channel-axis field's bytes is O(channels) and scales with channel count on this unconditional hot path (measured ~0.5 us at 256 ch, ~1.2 us at 1024 ch vs a ~0.07 us presence check).

Fold only an O(1) boolean for whether the axis carries the target structured field. Concession: a change in the field's *values* while the axis stays structured and the channel count is unchanged is no longer detected. That is safe for real acquisition streams, whose channel->field map is static for the stream's life; a genuine remap arrives with a new key or a different channel count, both already in the hash.

Invert the re-derivation test accordingly: it now asserts the live-remap value change is deliberately NOT re-derived, and that a new key (the escape hatch) forces re-derivation.
CommonRereference with include_current=False computes a leave-one-out
reference as (N/(N-1))*mean - x/(N-1). For a cluster of size 1 this
divides by N-1 == 0, raising ZeroDivisionError mid-stream. Previously
only reachable by hand-writing a singleton channel_clusters entry, it is
now reachable automatically via cluster_by_field when a derived group
(e.g. an electrode bank) contains a lone channel.

A single channel has no "other" channels to reference against, so pass
it through unchanged (reference of zero) instead of dividing by zero.

Add test_common_rereference_singleton_cluster_exclude_current: a lone
bank passes through while a larger bank still does leave-one-out.

@cboulay cboulay left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a change to not do quite as rigorous of a hash because it's quite expensive for something that should almost never happen in practice.

Comment thread src/ezmsg/sigproc/affinetransform.py
@cboulay
cboulay merged commit 8bd6adb into dev Jul 2, 2026
36 of 38 checks passed
@cboulay
cboulay deleted the car-bank-aware branch July 2, 2026 01:05
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.

2 participants