channel cluster from field helper, car cluster by field - #162
Merged
Conversation
_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
approved these changes
Jul 2, 2026
cboulay
left a comment
Member
There was a problem hiding this comment.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a way to derive per-cluster rereferencing groups from a structured field
of the channel coordinate axis, so
CommonRereference(CAR) can rereferencewithin electrode banks without the caller hand-specifying index lists.
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
Nonewhen the axis carries no usable field (so callers can fall back).CommonRereferencegains acluster_by_fieldsetting. Precedence:explicit
channel_clusters>cluster_by_fieldderivation > singleall-channel cluster. Default
Nonepreserves current behavior._hash_messagenow folds the field's bytes into the state hash on thecluster_by_fieldpath, so cached clusters re-derive if the field's valueschange 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 everyNonefallback) + bank-aware CAR cases intest_affine_transform.py. 54 pass.