Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ on:
- dev
workflow_dispatch:

# A new push to a PR supersedes the run already in flight for that ref, so a
# wedged job cannot sit on a runner while its replacement queues behind it.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
strategy:
Expand All @@ -21,6 +27,7 @@ jobs:
- "windows-latest"
- "macos-latest"
runs-on: ${{matrix.os}}
timeout-minutes: 30

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -53,6 +60,7 @@ jobs:
minimal-install:
name: Minimal install (no extras)
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- uses: actions/checkout@v4
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ license = "MIT"
requires-python = ">=3.10"
dynamic = ["version"]
dependencies = [
# 3.10.0b2 for CoordinateAxis.fingerprint and AxisArray.chunk_dim, which
# 3.10.0b3 for CoordinateAxis.fingerprint and AxisArray.stream_dim, which
# ezmsg-baseproc's default state hash reads. Pinned directly rather than
# left to the transitive requirement: uv only enables pre-releases for a
# package named with a pre-release marker in *this* file.
"ezmsg>=3.10.0b2",
"ezmsg>=3.10.0b3",
"ezmsg-baseproc>=1.13.0",
"ezmsg-sigproc>=3.8.1", # Window/Resample must resolve chunk_dim from axis=None
"ezmsg-sigproc>=3.8.1", # Window/Resample must resolve stream_dim from axis=None
"numpy",
"scipy",
"array-api-compat",
Expand Down
4 changes: 2 additions & 2 deletions src/ezmsg/learn/collection/sample_adapt_regressor.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ class SampleAdaptRegressorSettings(ez.Settings):
""".. deprecated:: 1.6
Scheduled for removal in 2.0. Resampling buffers along the dimension
messages accumulate along, which now comes from
:attr:`~ezmsg.util.messages.axisarray.AxisArray.chunk_dim`."""
:attr:`~ezmsg.util.messages.axisarray.AxisArray.stream_dim`."""

def __post_init__(self) -> None:
warn_axis_deprecated(self, "resample_axis", package="ezmsg-learn", removal="2.0")
Expand Down Expand Up @@ -282,7 +282,7 @@ def configure(self) -> None:
if use_window:
self.WINDOW.apply_settings(
WindowSettings(
# No `axis`: Window follows the stream's chunk_dim, which
# No `axis`: Window follows the stream's stream_dim, which
# is what "time" was standing in for.
newaxis="win",
window_dur=self.SETTINGS.decode_window_dur,
Expand Down
12 changes: 6 additions & 6 deletions src/ezmsg/learn/dim_reduce/adaptive_decomp.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
BaseAdaptiveTransformer,
BaseAdaptiveTransformerUnit,
processor_state,
resolve_chunk_dim,
resolve_stream_dim,
warn_axis_deprecated,
)
from ezmsg.util.messages.axisarray import AxisArray, replace
Expand All @@ -37,12 +37,12 @@ class AdaptiveDecompSettings(ez.Settings):

``None`` (default) decomposes every dimension except the one messages
accumulate along, iterating over that one. Naming a dimension (e.g.
``"ch"``) decomposes it and iterates over the chunk dimension instead.
``"ch"``) decomposes it and iterates over the stream dimension instead.

.. deprecated:: 1.6
The ``"!time"`` spelling -- "iterate over time" -- is scheduled for
removal in 2.0. It hardcodes what
:attr:`~ezmsg.util.messages.axisarray.AxisArray.chunk_dim` now
:attr:`~ezmsg.util.messages.axisarray.AxisArray.stream_dim` now
answers; leave this unset for the same behaviour."""

def __post_init__(self) -> None:
Expand Down Expand Up @@ -101,7 +101,7 @@ def _calculate_axis_groups(self, message: AxisArray):
# Iterate over the dimension messages accumulate along and collapse
# every other one -- what "!time" spelled, with the dimension read
# off the stream instead of assumed.
iter_axis = resolve_chunk_dim(message, self.STREAMING_DIMS)
iter_axis = resolve_stream_dim(message, self.STREAMING_DIMS)
it_ax_ix = message.get_axis_idx(iter_axis)
targ_axes = message.dims[:it_ax_ix] + message.dims[it_ax_ix + 1 :]
off_targ_axes = []
Expand All @@ -116,8 +116,8 @@ def _calculate_axis_groups(self, message: AxisArray):
targ_axes = [axis]
# Iterate over the dimension messages accumulate along. This was a
# hand-rolled `"win" if "win" in dims else "time"` guess, which is
# exactly what chunk_dim exists to answer.
iter_axis = resolve_chunk_dim(message, self.STREAMING_DIMS)
# exactly what stream_dim exists to answer.
iter_axis = resolve_stream_dim(message, self.STREAMING_DIMS)
if iter_axis == axis:
raise ValueError(f"Iterating axis ({iter_axis}) cannot be the same as the target axis ({axis})")
it_ax_ix = message.get_axis_idx(iter_axis)
Expand Down
8 changes: 4 additions & 4 deletions src/ezmsg/learn/dim_reduce/incremental_decomp.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def _initialize_processors(
# Create windowing processor if update_interval is specified
if settings.update_interval > 0:
# Only the "!axis" spelling names the iteration dimension outright.
# Otherwise leave it to Window, which resolves chunk_dim from the
# Otherwise leave it to Window, which resolves stream_dim from the
# message -- this is the "likely incorrect" hardcoded "time" that
# used to be here, and there is no message to resolve from at this
# point anyway.
Expand Down Expand Up @@ -114,13 +114,13 @@ def _partial_fit_windowed(self, train_msg: AxisArray) -> None:
axis_idx = train_msg.get_axis_idx("win")
win_axis = train_msg.axes["win"]
offsets = win_axis.value(np.asarray(range(train_msg.data.shape[axis_idx])))
# Slicing "win" away leaves each sub-message no longer a chunk along
# Slicing "win" away leaves each sub-message no longer a stream along
# it. Newer ezmsg clears the declaration for us, but say what these
# slices *are* chunks along rather than leaving them undeclared:
# successive windows advance along the within-window axis, which is
# what the offset fix-up below re-anchors. Clearing it first keeps
# this working on ezmsg versions that do not.
unbundled = replace(train_msg, chunk_dim=None)
unbundled = replace(train_msg, stream_dim=None)
for ix, _msg in enumerate(unbundled.iter_over_axis("win")):
_msg = replace(
_msg,
Expand All @@ -131,7 +131,7 @@ def _partial_fit_windowed(self, train_msg: AxisArray) -> None:
offset=_msg.axes["time"].offset + offsets[ix],
),
},
chunk_dim="time",
stream_dim="time",
)
self._procs["decomp"].partial_fit(_msg)

Expand Down
8 changes: 4 additions & 4 deletions src/ezmsg/learn/process/flatten.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
BaseStatefulTransformer,
BaseTransformerUnit,
processor_state,
resolve_chunk_dim,
resolve_stream_dim,
)
from ezmsg.sigproc.flatten import (
FlattenSettings as SigprocFlattenSettings,
Expand Down Expand Up @@ -122,7 +122,7 @@ class FlattenTransformer(BaseStatefulTransformer[FlattenSettings, AxisArray, Axi
"""

STREAMING_DIMS = ("win",)
"""Fallback chunk dimension when the producer does not declare one.
"""Fallback stream dimension when the producer does not declare one.

The base class defaults to ``("time",)``, which is exactly wrong here: the
canonical input is ``(win, time, ch[, feature])``, where ``win`` is what
Expand All @@ -131,12 +131,12 @@ class FlattenTransformer(BaseStatefulTransformer[FlattenSettings, AxisArray, Axi
stop this noticing a window-length change, while including ``win`` would
rebuild the inner transformer every time the window count jittered.

Consulted only when :attr:`AxisArray.chunk_dim` is absent; a producer that
Consulted only when :attr:`AxisArray.stream_dim` is absent; a producer that
declares it -- ezmsg-sigproc's ``Window`` does -- overrides this.
"""

def _reset_state(self, message: AxisArray) -> None:
preserve_axis = self.settings.preserve_axis or resolve_chunk_dim(message, self.STREAMING_DIMS)
preserve_axis = self.settings.preserve_axis or resolve_stream_dim(message, self.STREAMING_DIMS)
sample_axis = self.settings.sample_axis or preserve_axis
feature_axis = self.settings.feature_axis

Expand Down
6 changes: 3 additions & 3 deletions src/ezmsg/learn/process/sgd.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
BaseAdaptiveTransformer,
BaseAdaptiveTransformerUnit,
processor_state,
resolve_chunk_dim,
resolve_stream_dim,
)
from ezmsg.util.messages.axisarray import AxisArray
from ezmsg.util.messages.util import replace
Expand Down Expand Up @@ -37,7 +37,7 @@ class SGDDecoderState:

class SGDDecoderTransformer(BaseAdaptiveTransformer[SGDDecoderSettings, AxisArray, ClassifierMessage, SGDDecoderState]):
STREAMING_DIMS = ("win", "time")
"""This decoder is fed windows, so a producer that declares no ``chunk_dim``
"""This decoder is fed windows, so a producer that declares no ``stream_dim``
is accumulating along ``win`` rather than ``time``. The base default would
guess ``time`` and flatten the windows into the feature vector."""

Expand Down Expand Up @@ -96,7 +96,7 @@ def _process(self, message: AxisArray) -> ClassifierMessage | None:
if np.any(np.isnan(message.data)):
return None
try:
chunk = resolve_chunk_dim(message, self.STREAMING_DIMS)
chunk = resolve_stream_dim(message, self.STREAMING_DIMS)
chunk_idx = message.get_axis_idx(chunk)
data = message.data if chunk_idx == 0 else np.moveaxis(message.data, chunk_idx, 0)
X = data.reshape((data.shape[0], -1))
Expand Down
8 changes: 4 additions & 4 deletions src/ezmsg/learn/process/slda.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
BaseStatefulTransformer,
BaseTransformerUnit,
processor_state,
resolve_configured_chunk_dim,
resolve_configured_stream_dim,
warn_axis_deprecated,
)
from ezmsg.util.messages.axisarray import AxisArray
Expand All @@ -39,7 +39,7 @@ class SLDASettings(ez.Settings):
Scheduled for removal in 2.0. The samples this classifies accumulate
along one dimension, and the cached output template is keyed to it;
that dimension now comes from
:attr:`~ezmsg.util.messages.axisarray.AxisArray.chunk_dim`."""
:attr:`~ezmsg.util.messages.axisarray.AxisArray.stream_dim`."""

def __post_init__(self) -> None:
warn_axis_deprecated(self, package="ezmsg-learn", removal="2.0")
Expand All @@ -48,15 +48,15 @@ def __post_init__(self) -> None:
@processor_state
class SLDAState:
axis: str = ""
"""The resolved chunk dimension, fixed at reset so every later use agrees."""
"""The resolved stream dimension, fixed at reset so every later use agrees."""

lda: LDA
out_template: typing.Optional[ClassifierMessage] = None


class SLDATransformer(BaseStatefulTransformer[SLDASettings, AxisArray, ClassifierMessage, SLDAState]):
def _reset_state(self, message: AxisArray) -> None:
self.state.axis = resolve_configured_chunk_dim(self, message, self.settings.axis, legacy_default="time")
self.state.axis = resolve_configured_stream_dim(self, message, self.settings.axis, legacy_default="time")
if self.settings.settings_path[-4:] == ".mat":
# Expects a very specific format from a specific project. Not for general use.
import scipy.io as sio
Expand Down
2 changes: 1 addition & 1 deletion src/ezmsg/learn/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def with_fingerprint(axis: AxisArray.CoordinateAxis) -> AxisArray.CoordinateAxis
first consumer in every receiving process, on every message.

Apply it to axes that describe the stream -- channel labels, class labels,
lag labels -- not to per-message coordinates along the chunk dimension,
lag labels -- not to per-message coordinates along the stream dimension,
whose fingerprint no consumer reads.
"""
axis.fingerprint
Expand Down
2 changes: 1 addition & 1 deletion tests/dim_reduce/test_incremental_decomp.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def test_initialization_pca(self, pca_test_data, update_interval):
if update_interval > 0:
assert "windowing" in transformer._procs
win = transformer._procs["windowing"]
# Unset on purpose: Window resolves the stream's chunk_dim, which is
# Unset on purpose: Window resolves the stream's stream_dim, which is
# what the old hardcoded "time" was standing in for (and got wrong
# downstream of another windowing stage).
assert win.settings.axis is None
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/test_axis_fingerprint_priming.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def signal(labels, n_time=32, fs=100.0, key="dev"):
"ch": CoordinateAxis(data=np.array(labels), dims=["ch"]),
},
key=key,
chunk_dim="time",
stream_dim="time",
)


Expand Down Expand Up @@ -65,7 +65,7 @@ def test_priming_survives_the_transport(self, dtype):


class TestCreatedAxesArePrimed:
"""Messages here carry no ``chunk_dim``: released ezmsg-sigproc does not set
"""Messages here carry no ``stream_dim``: released ezmsg-sigproc does not set
it, so that is what these transformers actually receive today. It is why
``FlattenTransformer.STREAMING_DIMS`` names ``win`` -- the base class's
``("time",)`` fallback would exclude the lag dimension, which is the one
Expand Down
Loading