Skip to content

LRR: pass clusters below MIN_REREF_CLUSTER_SIZE through untouched - #18

Merged
cboulay merged 3 commits into
devfrom
lrr-low-channel-passthrough
Jul 21, 2026
Merged

LRR: pass clusters below MIN_REREF_CLUSTER_SIZE through untouched#18
cboulay merged 3 commits into
devfrom
lrr-low-channel-passthrough

Conversation

@kylmcgr

@kylmcgr kylmcgr commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Makes LRR (Linear Regression Rereferencing) robust when a cluster has very few channels, or when the input has none. Clusters below a minimum size are passed through unchanged (identity), and 0-channel inputs no longer crash.

Motivation

LRR rereferences each channel by regressing it against the other channels in its cluster. When a cluster is tiny, that reference isn't meaningful — 1 channel has nothing to regress against, and 2 leaves a single, noisy reference that's as likely to inject noise as remove common mode. Today only k <= 1 is skipped, and an empty channel set crashes outright.

This surfaces when the channel stream is sliced upstream — e.g. selecting a region/bank subset before LRR can leave a cluster with a handful of channels, or leave a whole hub with zero channels. Those cases should be no-ops, not crashes or unstable fits.

Changes

  • Add MIN_REREF_CLUSTER_SIZE module constant (default 3). Clusters with fewer channels are left untouched (their W rows stay 0 → identity). Previously the skip was k <= 1. Rationale: a cluster needs ≥2 references to be worth rereferencing; the value is a const so it's easy to tune, and can be promoted to a setting if callers want to control it.
  • Make a 0-channel input a clean passthrough rather than a crash. This needed guards at three separate points (it's not covered by the < 3 rule alone, since with 0 channels there are no clusters to skip):
    • _validate_clusters — tolerate an empty cluster list (was np.concatenate([]))
    • partial_fit — early return on 0 channels (was reshape(data, (-1, 0)))
    • _process — passthrough on 0 channels (avoids building an AffineTransform from empty clusters)

Behavior change (note for reviewers)

This is a shared-library behavior change: a 2-channel cluster is now passed through instead of rereferenced. In normal operation this is invisible — electrode banks are 32 channels, far above the threshold — so only deliberately small or empty slices exercise the new path. Larger clusters are unaffected (verified identical weights for full banks).

Testing

  • Added TestLowChannelPassthrough (5 cases): 0 channels (no crash), 1 and 2 channels (identity passthrough), exactly MIN_REREF_CLUSTER_SIZE (rereferenced), and a mixed message where a full bank is rereferenced while a lone-channel bank passes through.
  • Full tests/unit/test_ssr.py suite passes (26 passed), including all pre-existing tests — no regressions.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the Linear Regression Rereferencing (LRR) implementation in ezmsg.learn.process.ssr for edge cases involving very small channel clusters and fully empty (0-channel) inputs, preventing crashes and ensuring predictable passthrough behavior for undersized clusters.

Changes:

  • Introduces MIN_REREF_CLUSTER_SIZE (default 3) so clusters smaller than this threshold are left as identity (no rereference).
  • Adds explicit 0-channel guards in cluster validation, partial_fit, and _process to avoid crashes and ensure clean passthrough.
  • Adds unit tests covering 0/1/2-channel passthrough, threshold behavior, and mixed small/large clusters.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/ezmsg/learn/process/ssr.py Adds minimum cluster size logic and 0-channel passthrough guards to make LRR robust to sliced/empty channel inputs.
tests/unit/test_ssr.py Adds regression tests to verify identity passthrough for undersized/empty clusters and rereferencing at/above the threshold.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/ezmsg/learn/process/ssr.py Outdated
kylmcgr and others added 2 commits July 20, 2026 12:02
partial_fit and _process already tolerate 0-channel input; fit() still
crashed on reshape(-1, 0). Guard it the same way, with a test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cboulay
cboulay merged commit 0a01cd7 into dev Jul 21, 2026
8 checks passed
@cboulay
cboulay deleted the lrr-low-channel-passthrough branch July 21, 2026 23: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.

3 participants