Slicer: add on_empty setting — no-match selections warn and emit empty by default - #177
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds an opt-in allow_empty flag to the Slicer stack so that label/regex selections that match nothing can yield an empty (0-length) result instead of raising, while preserving the existing fail-fast behavior by default.
Changes:
- Extend
parse_slice(...)withallow_empty=Falseto optionally treat no-match tokens as “no indices” rather than raising. - Thread
allow_emptythroughSlicerSettings,SlicerTransformer._reset_state, and theslicer()factory; guard the empty-selection case to avoidnp.hstack([])and emit a warning. - Add unit tests covering both
parse_slice(..., allow_empty=True)behavior and end-to-end empty outputs fromSlicerTransformer.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/ezmsg/sigproc/slicer.py | Adds allow_empty plumbed through parsing, settings, transformer state reset, and the slicer() factory; handles empty selections safely and warns. |
| tests/unit/test_slicer.py | Adds tests verifying allow-empty parsing behavior and that slicer emits 0-length channel outputs instead of raising when configured. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| allow_empty: (Optional) If True, a label/regex token that matches nothing | ||
| returns no indices instead of raising. In a comma-separated selection, | ||
| non-matching tokens are dropped and the matching ones kept; if every | ||
| token matches nothing the result is an empty tuple (a 0-length slice). |
… warn mode - SlicerSettings.on_empty: 'raise' (default, unchanged behavior) or 'warn'. - In warn mode, single-entry matches keep a length-1 axis instead of dropping the dimension (or crashing on exact-label matches via np.int64), so output rank is stable regardless of how many entries match. - Log dropped non-matching tokens at info level in warn mode. - parse_slice: cast exact-label hits to Python ints (np.int64 failed the isinstance(_, int) dim-drop check, producing 0-d axis data).
- on_empty now defaults to 'warn': a no-match selection emits an empty (0-length) result with a warning instead of raising; 'raise' remains available as the strict opt-in. - Rank semantics no longer depend on on_empty: label/regex selections always preserve the sliced axis (a single matching entry yields a length-1 axis); only a bare-integer positional selection (e.g. '5') drops the dimension, preserving the documented parse_slice behavior in both modes.
|
Thanks @kylmcgr — the motivation and the empty-result mechanics here were solid, and the original opt-in behavior is preserved (as 4699a68 —
|
Summary
Adds an opt-in
allow_emptysetting toSlicer. By default a label/regex selection that matches nothing still raises; withallow_empty=True, non-matching tokens are dropped and a selection that matches nothing yields an empty (0-length) result plus a one-time warning.Motivation
Today a label/regex selection that matches no labels raises
ValueError("… matched no labels …"). That fail-fast is valuable — it catches typos, wrong-axis, and wrong-label mistakes — and stays the default.But some callers apply the same selection across multiple streams where a given stream may legitimately contain none of the selected entries. The concrete case: a per-source region selection (e.g.
".*-aip-.*,.*-smg-.*") broadcast to every acquisition hub, where one hub carries none of those regions. There, "matched nothing" is expected, not an error — that source should simply contribute no channels. For those callers, raising is wrong; they want an empty result to flow through.Changes
SlicerSettings.allow_empty: bool = False.parse_slice(..., allow_empty=False): when a label/regex token matches nothing, return no indices instead of raising. In a comma-separated selection, non-matching tokens are dropped and matching ones kept (in order); if every token matches nothing the result is an empty tuple.SlicerTransformer._reset_state: passallow_emptythrough, guard the empty-result case (0-length selection along the axis, avoidingnp.hstack([])), and log a warning once per stream configuration when the selection resolves to empty.slicer()factory.Behavior change
None by default —
allow_empty=Falsepreserves the existing raise exactly. The new behavior is strictly opt-in. Matching selections are unaffected regardless of the flag.Testing
test_parse_slice_allow_empty: non-matching token →()(vs raise by default); comma list drops non-matching and keeps matching in order; all-non-matching →(); matching selection unaffected.test_slicer_allow_empty_emits_empty: default raises;allow_empty=Trueon a no-match yields a(time, 0)output with a 0-lengthchaxis (time axis intact); a partial match keeps only the matching channels.tests/unit/test_slicer.pypasses (17 passed), including all pre-existing tests — no regressions.