ezmsg-sigproc 2.7.0__py3-none-any.whl → 2.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '2.7.0'
32
- __version_tuple__ = version_tuple = (2, 7, 0)
31
+ __version__ = version = '2.9.0'
32
+ __version_tuple__ = version_tuple = (2, 9, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -1,3 +1,13 @@
1
+ """Affine transformations via matrix multiplication: y = Ax or y = Ax + B.
2
+
3
+ For full matrix transformations where channels are mixed (off-diagonal weights),
4
+ use :obj:`AffineTransformTransformer` or the `AffineTransform` unit.
5
+
6
+ For simple per-channel scaling and offset (diagonal weights only), use
7
+ :obj:`LinearTransformTransformer` from :mod:`ezmsg.sigproc.linear` instead,
8
+ which is more efficient as it avoids matrix multiplication.
9
+ """
10
+
1
11
  import os
2
12
  from pathlib import Path
3
13
 
@@ -17,7 +27,6 @@ from ezmsg.util.messages.util import replace
17
27
  class AffineTransformSettings(ez.Settings):
18
28
  """
19
29
  Settings for :obj:`AffineTransform`.
20
- See :obj:`affine_transform` for argument details.
21
30
  """
22
31
 
23
32
  weights: np.ndarray | str | Path
@@ -39,6 +48,19 @@ class AffineTransformState:
39
48
  class AffineTransformTransformer(
40
49
  BaseStatefulTransformer[AffineTransformSettings, AxisArray, AxisArray, AffineTransformState]
41
50
  ):
51
+ """Apply affine transformation via matrix multiplication: y = Ax or y = Ax + B.
52
+
53
+ Use this transformer when you need full matrix transformations that mix
54
+ channels (off-diagonal weights), such as spatial filters or projections.
55
+
56
+ For simple per-channel scaling and offset where each output channel depends
57
+ only on its corresponding input channel (diagonal weight matrix), use
58
+ :obj:`LinearTransformTransformer` instead, which is more efficient.
59
+
60
+ The weights matrix can include an offset row (stacked as [A|B]) where the
61
+ input is automatically augmented with a column of ones to compute y = Ax + B.
62
+ """
63
+
42
64
  def __call__(self, message: AxisArray) -> AxisArray:
43
65
  # Override __call__ so we can shortcut if weights are None.
44
66
  if self.settings.weights is None or (
@@ -1,3 +1,12 @@
1
+ """
2
+ Aggregation operations over arrays.
3
+
4
+ .. note::
5
+ :obj:`AggregateTransformer` supports the :doc:`Array API standard </guides/explanations/array_api>`,
6
+ enabling use with NumPy, CuPy, PyTorch, and other compatible array libraries.
7
+ :obj:`RangedAggregateTransformer` currently requires NumPy arrays.
8
+ """
9
+
1
10
  import typing
2
11
 
3
12
  import ezmsg.core as ez
@@ -4,10 +4,10 @@ import typing
4
4
  import ezmsg.core as ez
5
5
  import numpy as np
6
6
  import scipy.signal
7
+ from ezmsg.baseproc import SettingsType
7
8
  from ezmsg.util.messages.axisarray import AxisArray
8
9
  from ezmsg.util.messages.util import replace
9
10
 
10
- from ezmsg.sigproc.base import SettingsType
11
11
  from ezmsg.sigproc.butterworthfilter import ButterworthFilterSettings, butter_design_fun
12
12
  from ezmsg.sigproc.filter import (
13
13
  BACoeffs,
@@ -3,6 +3,10 @@ Coordinate space transformations for streaming data.
3
3
 
4
4
  This module provides utilities and ezmsg nodes for transforming between
5
5
  Cartesian (x, y) and polar (r, theta) coordinate systems.
6
+
7
+ .. note::
8
+ This module supports the :doc:`Array API standard </guides/explanations/array_api>`,
9
+ enabling use with NumPy, CuPy, PyTorch, and other compatible array libraries.
6
10
  """
7
11
 
8
12
  from enum import Enum
@@ -11,6 +15,7 @@ from typing import Tuple
11
15
  import ezmsg.core as ez
12
16
  import numpy as np
13
17
  import numpy.typing as npt
18
+ from array_api_compat import get_namespace, is_array_api_obj
14
19
  from ezmsg.baseproc import (
15
20
  BaseTransformer,
16
21
  BaseTransformerUnit,
@@ -20,14 +25,24 @@ from ezmsg.util.messages.axisarray import AxisArray, replace
20
25
  # -- Utility functions for coordinate transformations --
21
26
 
22
27
 
28
+ def _get_namespace_or_numpy(*args: npt.ArrayLike):
29
+ """Get array namespace if any arg is an array, otherwise return numpy."""
30
+ for arg in args:
31
+ if is_array_api_obj(arg):
32
+ return get_namespace(arg)
33
+ return np
34
+
35
+
23
36
  def polar2z(r: npt.ArrayLike, theta: npt.ArrayLike) -> npt.ArrayLike:
24
37
  """Convert polar coordinates to complex number representation."""
25
- return r * np.exp(1j * theta)
38
+ xp = _get_namespace_or_numpy(r, theta)
39
+ return r * xp.exp(1j * theta)
26
40
 
27
41
 
28
42
  def z2polar(z: npt.ArrayLike) -> Tuple[npt.ArrayLike, npt.ArrayLike]:
29
43
  """Convert complex number to polar coordinates (r, theta)."""
30
- return np.abs(z), np.angle(z)
44
+ xp = _get_namespace_or_numpy(z)
45
+ return xp.abs(z), xp.atan2(xp.imag(z), xp.real(z))
31
46
 
32
47
 
33
48
  def cart2z(x: npt.ArrayLike, y: npt.ArrayLike) -> npt.ArrayLike:
@@ -37,7 +52,8 @@ def cart2z(x: npt.ArrayLike, y: npt.ArrayLike) -> npt.ArrayLike:
37
52
 
38
53
  def z2cart(z: npt.ArrayLike) -> Tuple[npt.ArrayLike, npt.ArrayLike]:
39
54
  """Convert complex number to Cartesian coordinates (x, y)."""
40
- return np.real(z), np.imag(z)
55
+ xp = _get_namespace_or_numpy(z)
56
+ return xp.real(z), xp.imag(z)
41
57
 
42
58
 
43
59
  def cart2pol(x: npt.ArrayLike, y: npt.ArrayLike) -> Tuple[npt.ArrayLike, npt.ArrayLike]:
@@ -90,6 +106,7 @@ class CoordinateSpacesTransformer(BaseTransformer[CoordinateSpacesSettings, Axis
90
106
  """
91
107
 
92
108
  def _process(self, message: AxisArray) -> AxisArray:
109
+ xp = get_namespace(message.data)
93
110
  axis = self.settings.axis or message.dims[-1]
94
111
  axis_idx = message.get_axis_idx(axis)
95
112
 
@@ -116,9 +133,9 @@ class CoordinateSpacesTransformer(BaseTransformer[CoordinateSpacesSettings, Axis
116
133
  out_a, out_b = pol2cart(component_a, component_b)
117
134
 
118
135
  # Stack results back along the same axis
119
- result = np.stack([out_a, out_b], axis=axis_idx)
136
+ result = xp.stack([out_a, out_b], axis=axis_idx)
120
137
 
121
- # Update axis labels if present
138
+ # Update axis labels if present (use numpy for string labels)
122
139
  axes = message.axes
123
140
  if axis in axes and hasattr(axes[axis], "data"):
124
141
  if self.settings.mode == CoordinateMode.CART2POL:
@@ -1,14 +1,13 @@
1
1
  import ezmsg.core as ez
2
2
  import numpy as np
3
3
  import numpy.typing as npt
4
- from ezmsg.util.messages.axisarray import AxisArray
5
- from ezmsg.util.messages.util import replace
6
-
7
- from ezmsg.sigproc.base import (
4
+ from ezmsg.baseproc import (
8
5
  BaseStatefulTransformer,
9
6
  BaseTransformerUnit,
10
7
  processor_state,
11
8
  )
9
+ from ezmsg.util.messages.axisarray import AxisArray
10
+ from ezmsg.util.messages.util import replace
12
11
 
13
12
 
14
13
  class DenormalizeSettings(ez.Settings):
ezmsg/sigproc/detrend.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import scipy.signal as sps
2
+ from ezmsg.baseproc import BaseTransformerUnit
2
3
  from ezmsg.util.messages.axisarray import AxisArray, replace
3
4
 
4
- from ezmsg.sigproc.base import BaseTransformerUnit
5
5
  from ezmsg.sigproc.ewma import EWMASettings, EWMATransformer
6
6
 
7
7
 
ezmsg/sigproc/diff.py CHANGED
@@ -1,14 +1,22 @@
1
+ """
2
+ Compute differences along an axis.
3
+
4
+ .. note::
5
+ This module supports the :doc:`Array API standard </guides/explanations/array_api>`,
6
+ enabling use with NumPy, CuPy, PyTorch, and other compatible array libraries.
7
+ """
8
+
1
9
  import ezmsg.core as ez
2
10
  import numpy as np
3
11
  import numpy.typing as npt
4
- from ezmsg.util.messages.axisarray import AxisArray, slice_along_axis
5
- from ezmsg.util.messages.util import replace
6
-
7
- from ezmsg.sigproc.base import (
12
+ from array_api_compat import get_namespace
13
+ from ezmsg.baseproc import (
8
14
  BaseStatefulTransformer,
9
15
  BaseTransformerUnit,
10
16
  processor_state,
11
17
  )
18
+ from ezmsg.util.messages.axisarray import AxisArray, slice_along_axis
19
+ from ezmsg.util.messages.util import replace
12
20
 
13
21
 
14
22
  class DiffSettings(ez.Settings):
@@ -40,23 +48,25 @@ class DiffTransformer(BaseStatefulTransformer[DiffSettings, AxisArray, AxisArray
40
48
  self.state.last_time = ax_info.data[0] - 0.001
41
49
 
42
50
  def _process(self, message: AxisArray) -> AxisArray:
51
+ xp = get_namespace(message.data)
43
52
  axis = self.settings.axis or message.dims[0]
44
53
  ax_idx = message.get_axis_idx(axis)
45
54
 
46
- diffs = np.diff(
47
- np.concatenate((self.state.last_dat, message.data), axis=ax_idx),
55
+ diffs = xp.diff(
56
+ xp.concat((self.state.last_dat, message.data), axis=ax_idx),
48
57
  axis=ax_idx,
49
58
  )
50
59
  # Prepare last_dat for next iteration
51
60
  self.state.last_dat = slice_along_axis(message.data, slice(-1, None), axis=ax_idx)
52
- # Scale by fs if requested. This convers the diff to a derivative. e.g., diff of position becomes velocity.
61
+ # Scale by fs if requested. This converts the diff to a derivative. e.g., diff of position becomes velocity.
53
62
  if self.settings.scale_by_fs:
54
63
  ax_info = message.get_axis(axis)
55
64
  if hasattr(ax_info, "data"):
65
+ # ax_info.data is typically numpy for metadata, so use np.diff here
56
66
  dt = np.diff(np.concatenate(([self.state.last_time], ax_info.data)))
57
67
  # Expand dt dims to match diffs
58
68
  exp_sl = (None,) * ax_idx + (Ellipsis,) + (None,) * (message.data.ndim - ax_idx - 1)
59
- diffs /= dt[exp_sl]
69
+ diffs /= xp.asarray(dt[exp_sl])
60
70
  self.state.last_time = ax_info.data[-1] # For next iteration
61
71
  else:
62
72
  diffs /= ax_info.gain
ezmsg/sigproc/ewma.py CHANGED
@@ -139,8 +139,15 @@ class EWMA_Deprecated:
139
139
 
140
140
  class EWMASettings(ez.Settings):
141
141
  time_constant: float = 1.0
142
+ """The amount of time for the smoothed response of a unit step function to reach 1 - 1/e approx-eq 63.2%."""
143
+
142
144
  axis: str | None = None
143
145
 
146
+ accumulate: bool = True
147
+ """If True, update the EWMA state with each sample. If False, only apply
148
+ the current EWMA estimate without updating state (useful for inference
149
+ periods where you don't want to adapt statistics)."""
150
+
144
151
 
145
152
  @processor_state
146
153
  class EWMAState:
@@ -166,15 +173,45 @@ class EWMATransformer(BaseStatefulTransformer[EWMASettings, AxisArray, AxisArray
166
173
  return message
167
174
  axis = self.settings.axis or message.dims[0]
168
175
  axis_idx = message.get_axis_idx(axis)
169
- expected, self._state.zi = sps.lfilter(
170
- [self._state.alpha],
171
- [1.0, self._state.alpha - 1.0],
172
- message.data,
173
- axis=axis_idx,
174
- zi=self._state.zi,
175
- )
176
+ if self.settings.accumulate:
177
+ # Normal behavior: update state with new samples
178
+ expected, self._state.zi = sps.lfilter(
179
+ [self._state.alpha],
180
+ [1.0, self._state.alpha - 1.0],
181
+ message.data,
182
+ axis=axis_idx,
183
+ zi=self._state.zi,
184
+ )
185
+ else:
186
+ # Process-only: compute output without updating state
187
+ expected, _ = sps.lfilter(
188
+ [self._state.alpha],
189
+ [1.0, self._state.alpha - 1.0],
190
+ message.data,
191
+ axis=axis_idx,
192
+ zi=self._state.zi,
193
+ )
176
194
  return replace(message, data=expected)
177
195
 
178
196
 
179
197
  class EWMAUnit(BaseTransformerUnit[EWMASettings, AxisArray, AxisArray, EWMATransformer]):
180
198
  SETTINGS = EWMASettings
199
+
200
+ @ez.subscriber(BaseTransformerUnit.INPUT_SETTINGS)
201
+ async def on_settings(self, msg: EWMASettings) -> None:
202
+ """
203
+ Handle settings updates with smart reset behavior.
204
+
205
+ Only resets state if `axis` changes (structural change).
206
+ Changes to `time_constant` or `accumulate` are applied without
207
+ resetting accumulated state.
208
+ """
209
+ old_axis = self.SETTINGS.axis
210
+ self.apply_settings(msg)
211
+
212
+ if msg.axis != old_axis:
213
+ # Axis changed - need full reset
214
+ self.create_processor()
215
+ else:
216
+ # Only accumulate or time_constant changed - keep state
217
+ self.processor.settings = msg
@@ -1,9 +1,8 @@
1
1
  import ezmsg.core as ez
2
2
  import numpy as np
3
+ from ezmsg.baseproc import BaseTransformer, BaseTransformerUnit
3
4
  from ezmsg.util.messages.axisarray import AxisArray, replace
4
5
 
5
- from ezmsg.sigproc.base import BaseTransformer, BaseTransformerUnit
6
-
7
6
 
8
7
  class ExtractAxisSettings(ez.Settings):
9
8
  axis: str = "freq"
ezmsg/sigproc/filter.py CHANGED
@@ -6,10 +6,7 @@ import ezmsg.core as ez
6
6
  import numpy as np
7
7
  import numpy.typing as npt
8
8
  import scipy.signal
9
- from ezmsg.util.messages.axisarray import AxisArray
10
- from ezmsg.util.messages.util import replace
11
-
12
- from ezmsg.sigproc.base import (
9
+ from ezmsg.baseproc import (
13
10
  BaseConsumerUnit,
14
11
  BaseStatefulTransformer,
15
12
  BaseTransformerUnit,
@@ -17,6 +14,8 @@ from ezmsg.sigproc.base import (
17
14
  TransformerType,
18
15
  processor_state,
19
16
  )
17
+ from ezmsg.util.messages.axisarray import AxisArray
18
+ from ezmsg.util.messages.util import replace
20
19
 
21
20
 
22
21
  @dataclass
@@ -4,10 +4,10 @@ import typing
4
4
  import ezmsg.core as ez
5
5
  import numpy as np
6
6
  import scipy.signal as sps
7
+ from ezmsg.baseproc import BaseStatefulTransformer, processor_state
7
8
  from ezmsg.util.messages.axisarray import AxisArray
8
9
  from ezmsg.util.messages.util import replace
9
10
 
10
- from ezmsg.sigproc.base import BaseStatefulTransformer, processor_state
11
11
  from ezmsg.sigproc.filter import (
12
12
  BACoeffs,
13
13
  BaseFilterByDesignTransformerUnit,
@@ -0,0 +1,120 @@
1
+ """
2
+ Apply a linear transformation: output = scale * input + offset.
3
+
4
+ Supports per-element scale and offset along a specified axis.
5
+ For full matrix transformations, use :obj:`AffineTransformTransformer` instead.
6
+
7
+ .. note::
8
+ This module supports the :doc:`Array API standard </guides/explanations/array_api>`,
9
+ enabling use with NumPy, CuPy, PyTorch, and other compatible array libraries.
10
+ """
11
+
12
+ import ezmsg.core as ez
13
+ import numpy as np
14
+ import numpy.typing as npt
15
+ from array_api_compat import get_namespace
16
+ from ezmsg.baseproc import BaseStatefulTransformer, BaseTransformerUnit, processor_state
17
+ from ezmsg.util.messages.axisarray import AxisArray
18
+ from ezmsg.util.messages.util import replace
19
+
20
+
21
+ class LinearTransformSettings(ez.Settings):
22
+ scale: float | list[float] | npt.ArrayLike = 1.0
23
+ """Scale factor(s). Can be a scalar (applied to all elements) or an array
24
+ matching the size of the specified axis for per-element scaling."""
25
+
26
+ offset: float | list[float] | npt.ArrayLike = 0.0
27
+ """Offset value(s). Can be a scalar (applied to all elements) or an array
28
+ matching the size of the specified axis for per-element offset."""
29
+
30
+ axis: str | None = None
31
+ """Axis along which to apply per-element scale/offset. If None, scalar
32
+ scale/offset are broadcast to all elements."""
33
+
34
+
35
+ @processor_state
36
+ class LinearTransformState:
37
+ scale: npt.NDArray = None
38
+ """Prepared scale array for broadcasting."""
39
+
40
+ offset: npt.NDArray = None
41
+ """Prepared offset array for broadcasting."""
42
+
43
+
44
+ class LinearTransformTransformer(
45
+ BaseStatefulTransformer[LinearTransformSettings, AxisArray, AxisArray, LinearTransformState]
46
+ ):
47
+ """Apply linear transformation: output = scale * input + offset.
48
+
49
+ This transformer is optimized for element-wise linear operations with
50
+ optional per-channel (or per-axis) coefficients. For full matrix
51
+ transformations, use :obj:`AffineTransformTransformer` instead.
52
+
53
+ Examples:
54
+ # Uniform scaling and offset
55
+ >>> transformer = LinearTransformTransformer(LinearTransformSettings(scale=2.0, offset=1.0))
56
+
57
+ # Per-channel scaling (e.g., for 3-channel data along "ch" axis)
58
+ >>> transformer = LinearTransformTransformer(LinearTransformSettings(
59
+ ... scale=[0.5, 1.0, 2.0],
60
+ ... offset=[0.0, 0.1, 0.2],
61
+ ... axis="ch"
62
+ ... ))
63
+ """
64
+
65
+ def _hash_message(self, message: AxisArray) -> int:
66
+ """Hash based on shape and axis to detect when broadcast shapes need recalculation."""
67
+ axis = self.settings.axis
68
+ if axis is not None:
69
+ axis_idx = message.get_axis_idx(axis)
70
+ return hash((message.data.ndim, axis_idx, message.data.shape[axis_idx]))
71
+ return hash(message.data.ndim)
72
+
73
+ def _reset_state(self, message: AxisArray) -> None:
74
+ """Prepare scale/offset arrays with proper broadcast shapes."""
75
+ xp = get_namespace(message.data)
76
+ ndim = message.data.ndim
77
+
78
+ scale = self.settings.scale
79
+ offset = self.settings.offset
80
+
81
+ # Convert settings to arrays
82
+ if isinstance(scale, (list, np.ndarray)):
83
+ scale = xp.asarray(scale, dtype=xp.float64)
84
+ else:
85
+ # Scalar: create a 0-d array
86
+ scale = xp.asarray(float(scale), dtype=xp.float64)
87
+
88
+ if isinstance(offset, (list, np.ndarray)):
89
+ offset = xp.asarray(offset, dtype=xp.float64)
90
+ else:
91
+ # Scalar: create a 0-d array
92
+ offset = xp.asarray(float(offset), dtype=xp.float64)
93
+
94
+ # If axis is specified and we have 1-d arrays, reshape for proper broadcasting
95
+ if self.settings.axis is not None and ndim > 0:
96
+ axis_idx = message.get_axis_idx(self.settings.axis)
97
+
98
+ if scale.ndim == 1:
99
+ # Create shape for broadcasting: all 1s except at axis_idx
100
+ broadcast_shape = [1] * ndim
101
+ broadcast_shape[axis_idx] = scale.shape[0]
102
+ scale = xp.reshape(scale, broadcast_shape)
103
+
104
+ if offset.ndim == 1:
105
+ broadcast_shape = [1] * ndim
106
+ broadcast_shape[axis_idx] = offset.shape[0]
107
+ offset = xp.reshape(offset, broadcast_shape)
108
+
109
+ self._state.scale = scale
110
+ self._state.offset = offset
111
+
112
+ def _process(self, message: AxisArray) -> AxisArray:
113
+ result = message.data * self._state.scale + self._state.offset
114
+ return replace(message, data=result)
115
+
116
+
117
+ class LinearTransform(BaseTransformerUnit[LinearTransformSettings, AxisArray, AxisArray, LinearTransformTransformer]):
118
+ """Unit wrapper for LinearTransformTransformer."""
119
+
120
+ SETTINGS = LinearTransformSettings
ezmsg/sigproc/math/abs.py CHANGED
@@ -1,12 +1,16 @@
1
- """Take the absolute value of the data."""
2
- # TODO: Array API
1
+ """
2
+ Take the absolute value of the data.
3
3
 
4
- import numpy as np
4
+ .. note::
5
+ This module supports the :doc:`Array API standard </guides/explanations/array_api>`,
6
+ enabling use with NumPy, CuPy, PyTorch, and other compatible array libraries.
7
+ """
8
+
9
+ from array_api_compat import get_namespace
10
+ from ezmsg.baseproc import BaseTransformer, BaseTransformerUnit
5
11
  from ezmsg.util.messages.axisarray import AxisArray
6
12
  from ezmsg.util.messages.util import replace
7
13
 
8
- from ..base import BaseTransformer, BaseTransformerUnit
9
-
10
14
 
11
15
  class AbsSettings:
12
16
  pass
@@ -14,7 +18,8 @@ class AbsSettings:
14
18
 
15
19
  class AbsTransformer(BaseTransformer[None, AxisArray, AxisArray]):
16
20
  def _process(self, message: AxisArray) -> AxisArray:
17
- return replace(message, data=np.abs(message.data))
21
+ xp = get_namespace(message.data)
22
+ return replace(message, data=xp.abs(message.data))
18
23
 
19
24
 
20
25
  class Abs(BaseTransformerUnit[None, AxisArray, AxisArray, AbsTransformer]): ... # SETTINGS = None
ezmsg/sigproc/math/add.py CHANGED
@@ -5,12 +5,11 @@ import typing
5
5
  from dataclasses import dataclass, field
6
6
 
7
7
  import ezmsg.core as ez
8
+ from ezmsg.baseproc import BaseTransformer, BaseTransformerUnit
8
9
  from ezmsg.baseproc.util.asio import run_coroutine_sync
9
10
  from ezmsg.util.messages.axisarray import AxisArray
10
11
  from ezmsg.util.messages.util import replace
11
12
 
12
- from ..base import BaseTransformer, BaseTransformerUnit
13
-
14
13
  # --- Constant Addition (single input) ---
15
14
 
16
15
 
@@ -1,27 +1,32 @@
1
- """Clips the data to be within the specified range."""
2
- # TODO: Array API
1
+ """
2
+ Clips the data to be within the specified range.
3
+
4
+ .. note::
5
+ This module supports the :doc:`Array API standard </guides/explanations/array_api>`,
6
+ enabling use with NumPy, CuPy, PyTorch, and other compatible array libraries.
7
+ """
3
8
 
4
9
  import ezmsg.core as ez
5
- import numpy as np
10
+ from array_api_compat import get_namespace
11
+ from ezmsg.baseproc import BaseTransformer, BaseTransformerUnit
6
12
  from ezmsg.util.messages.axisarray import AxisArray
7
13
  from ezmsg.util.messages.util import replace
8
14
 
9
- from ..base import BaseTransformer, BaseTransformerUnit
10
-
11
15
 
12
16
  class ClipSettings(ez.Settings):
13
- a_min: float
14
- """Lower clip bound."""
17
+ min: float | None = None
18
+ """Lower clip bound. If None, no lower clipping is applied."""
15
19
 
16
- a_max: float
17
- """Upper clip bound."""
20
+ max: float | None = None
21
+ """Upper clip bound. If None, no upper clipping is applied."""
18
22
 
19
23
 
20
24
  class ClipTransformer(BaseTransformer[ClipSettings, AxisArray, AxisArray]):
21
25
  def _process(self, message: AxisArray) -> AxisArray:
26
+ xp = get_namespace(message.data)
22
27
  return replace(
23
28
  message,
24
- data=np.clip(message.data, self.settings.a_min, self.settings.a_max),
29
+ data=xp.clip(message.data, self.settings.min, self.settings.max),
25
30
  )
26
31
 
27
32
 
@@ -29,15 +34,15 @@ class Clip(BaseTransformerUnit[ClipSettings, AxisArray, AxisArray, ClipTransform
29
34
  SETTINGS = ClipSettings
30
35
 
31
36
 
32
- def clip(a_min: float, a_max: float) -> ClipTransformer:
37
+ def clip(min: float | None = None, max: float | None = None) -> ClipTransformer:
33
38
  """
34
- Clips the data to be within the specified range. See :obj:`np.clip` for more details.
39
+ Clips the data to be within the specified range.
35
40
 
36
41
  Args:
37
- a_min: Lower clip bound
38
- a_max: Upper clip bound
39
-
40
- Returns: :obj:`ClipTransformer`.
42
+ min: Lower clip bound. If None, no lower clipping is applied.
43
+ max: Upper clip bound. If None, no upper clipping is applied.
41
44
 
45
+ Returns:
46
+ :obj:`ClipTransformer`.
42
47
  """
43
- return ClipTransformer(ClipSettings(a_min=a_min, a_max=a_max))
48
+ return ClipTransformer(ClipSettings(min=min, max=max))
@@ -1,16 +1,22 @@
1
- """Take the difference between 2 signals or between a signal and a constant value."""
1
+ """
2
+ Take the difference between 2 signals or between a signal and a constant value.
3
+
4
+ .. note::
5
+ :obj:`ConstDifferenceTransformer` supports the :doc:`Array API standard </guides/explanations/array_api>`,
6
+ enabling use with NumPy, CuPy, PyTorch, and other compatible array libraries.
7
+ :obj:`DifferenceProcessor` (two-input difference) currently requires NumPy arrays.
8
+ """
2
9
 
3
10
  import asyncio
4
11
  import typing
5
12
  from dataclasses import dataclass, field
6
13
 
7
14
  import ezmsg.core as ez
15
+ from ezmsg.baseproc import BaseTransformer, BaseTransformerUnit
8
16
  from ezmsg.baseproc.util.asio import run_coroutine_sync
9
17
  from ezmsg.util.messages.axisarray import AxisArray
10
18
  from ezmsg.util.messages.util import replace
11
19
 
12
- from ..base import BaseTransformer, BaseTransformerUnit
13
-
14
20
 
15
21
  class ConstDifferenceSettings(ez.Settings):
16
22
  value: float = 0.0
@@ -1,10 +1,15 @@
1
- """1/data transformer."""
1
+ """
2
+ Compute the multiplicative inverse (1/x) of the data.
2
3
 
4
+ .. note::
5
+ This module supports the :doc:`Array API standard </guides/explanations/array_api>`,
6
+ enabling use with NumPy, CuPy, PyTorch, and other compatible array libraries.
7
+ """
8
+
9
+ from ezmsg.baseproc import BaseTransformer, BaseTransformerUnit
3
10
  from ezmsg.util.messages.axisarray import AxisArray
4
11
  from ezmsg.util.messages.util import replace
5
12
 
6
- from ..base import BaseTransformer, BaseTransformerUnit
7
-
8
13
 
9
14
  class InvertTransformer(BaseTransformer[None, AxisArray, AxisArray]):
10
15
  def _process(self, message: AxisArray) -> AxisArray:
ezmsg/sigproc/math/log.py CHANGED
@@ -1,13 +1,17 @@
1
- """Take the logarithm of the data."""
1
+ """
2
+ Take the logarithm of the data.
3
+
4
+ .. note::
5
+ This module supports the :doc:`Array API standard </guides/explanations/array_api>`,
6
+ enabling use with NumPy, CuPy, PyTorch, and other compatible array libraries.
7
+ """
2
8
 
3
- # TODO: Array API
4
9
  import ezmsg.core as ez
5
- import numpy as np
10
+ from array_api_compat import get_namespace
11
+ from ezmsg.baseproc import BaseTransformer, BaseTransformerUnit
6
12
  from ezmsg.util.messages.axisarray import AxisArray
7
13
  from ezmsg.util.messages.util import replace
8
14
 
9
- from ..base import BaseTransformer, BaseTransformerUnit
10
-
11
15
 
12
16
  class LogSettings(ez.Settings):
13
17
  base: float = 10.0
@@ -19,10 +23,17 @@ class LogSettings(ez.Settings):
19
23
 
20
24
  class LogTransformer(BaseTransformer[LogSettings, AxisArray, AxisArray]):
21
25
  def _process(self, message: AxisArray) -> AxisArray:
26
+ xp = get_namespace(message.data)
22
27
  data = message.data
23
- if self.settings.clip_zero and np.any(data <= 0) and np.issubdtype(data.dtype, np.floating):
24
- data = np.clip(data, a_min=np.finfo(data.dtype).tiny, a_max=None)
25
- return replace(message, data=np.log(data) / np.log(self.settings.base))
28
+ if self.settings.clip_zero:
29
+ # Check if any values are <= 0 and dtype is floating point
30
+ has_non_positive = bool(xp.any(data <= 0))
31
+ is_floating = xp.isdtype(data.dtype, "real floating")
32
+ if has_non_positive and is_floating:
33
+ # Use smallest_normal (Array API equivalent of numpy's finfo.tiny)
34
+ min_val = xp.finfo(data.dtype).smallest_normal
35
+ data = xp.clip(data, min_val, None)
36
+ return replace(message, data=xp.log(data) / xp.log(self.settings.base))
26
37
 
27
38
 
28
39
  class Log(BaseTransformerUnit[LogSettings, AxisArray, AxisArray, LogTransformer]):
@@ -1,11 +1,16 @@
1
- """Scale the data by a constant factor."""
1
+ """
2
+ Scale the data by a constant factor.
3
+
4
+ .. note::
5
+ This module supports the :doc:`Array API standard </guides/explanations/array_api>`,
6
+ enabling use with NumPy, CuPy, PyTorch, and other compatible array libraries.
7
+ """
2
8
 
3
9
  import ezmsg.core as ez
10
+ from ezmsg.baseproc import BaseTransformer, BaseTransformerUnit
4
11
  from ezmsg.util.messages.axisarray import AxisArray
5
12
  from ezmsg.util.messages.util import replace
6
13
 
7
- from ..base import BaseTransformer, BaseTransformerUnit
8
-
9
14
 
10
15
  class ScaleSettings(ez.Settings):
11
16
  scale: float = 1.0
@@ -3,14 +3,14 @@ from collections import deque
3
3
  import ezmsg.core as ez
4
4
  import numpy as np
5
5
  import numpy.typing as npt
6
- from ezmsg.util.messages.axisarray import AxisArray
7
- from ezmsg.util.messages.util import replace
8
-
9
- from ezmsg.sigproc.base import (
6
+ from ezmsg.baseproc import (
10
7
  BaseAdaptiveTransformer,
11
8
  BaseAdaptiveTransformerUnit,
12
9
  processor_state,
13
10
  )
11
+ from ezmsg.util.messages.axisarray import AxisArray
12
+ from ezmsg.util.messages.util import replace
13
+
14
14
  from ezmsg.sigproc.sampler import SampleMessage
15
15
 
16
16
 
ezmsg/sigproc/scaler.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import typing
2
2
 
3
+ import ezmsg.core as ez
3
4
  import numpy as np
4
5
  from ezmsg.baseproc import (
5
6
  BaseStatefulTransformer,
@@ -82,11 +83,38 @@ class AdaptiveStandardScalerTransformer(
82
83
  ]
83
84
  ):
84
85
  def _reset_state(self, message: AxisArray) -> None:
85
- self._state.samps_ewma = EWMATransformer(time_constant=self.settings.time_constant, axis=self.settings.axis)
86
- self._state.vars_sq_ewma = EWMATransformer(time_constant=self.settings.time_constant, axis=self.settings.axis)
86
+ self._state.samps_ewma = EWMATransformer(
87
+ time_constant=self.settings.time_constant,
88
+ axis=self.settings.axis,
89
+ accumulate=self.settings.accumulate,
90
+ )
91
+ self._state.vars_sq_ewma = EWMATransformer(
92
+ time_constant=self.settings.time_constant,
93
+ axis=self.settings.axis,
94
+ accumulate=self.settings.accumulate,
95
+ )
96
+
97
+ @property
98
+ def accumulate(self) -> bool:
99
+ """Whether to accumulate statistics from incoming samples."""
100
+ return self.settings.accumulate
101
+
102
+ @accumulate.setter
103
+ def accumulate(self, value: bool) -> None:
104
+ """
105
+ Set the accumulate mode and propagate to child EWMA transformers.
106
+
107
+ Args:
108
+ value: If True, update statistics with each sample.
109
+ If False, only apply current statistics without updating.
110
+ """
111
+ if self._state.samps_ewma is not None:
112
+ self._state.samps_ewma.settings = replace(self._state.samps_ewma.settings, accumulate=value)
113
+ if self._state.vars_sq_ewma is not None:
114
+ self._state.vars_sq_ewma.settings = replace(self._state.vars_sq_ewma.settings, accumulate=value)
87
115
 
88
116
  def _process(self, message: AxisArray) -> AxisArray:
89
- # Update step
117
+ # Update step (respects accumulate setting via child EWMAs)
90
118
  mean_message = self._state.samps_ewma(message)
91
119
  var_sq_message = self._state.vars_sq_ewma(replace(message, data=message.data**2))
92
120
 
@@ -108,6 +136,27 @@ class AdaptiveStandardScaler(
108
136
  ):
109
137
  SETTINGS = AdaptiveStandardScalerSettings
110
138
 
139
+ @ez.subscriber(BaseTransformerUnit.INPUT_SETTINGS)
140
+ async def on_settings(self, msg: AdaptiveStandardScalerSettings) -> None:
141
+ """
142
+ Handle settings updates with smart reset behavior.
143
+
144
+ Only resets state if `axis` changes (structural change).
145
+ Changes to `time_constant` or `accumulate` are applied without
146
+ resetting accumulated statistics.
147
+ """
148
+ old_axis = self.SETTINGS.axis
149
+ self.apply_settings(msg)
150
+
151
+ if msg.axis != old_axis:
152
+ # Axis changed - need full reset
153
+ self.create_processor()
154
+ else:
155
+ # Update accumulate on processor (propagates to child EWMAs)
156
+ self.processor.accumulate = msg.accumulate
157
+ # Also update own settings reference
158
+ self.processor.settings = msg
159
+
111
160
 
112
161
  # Backwards compatibility...
113
162
  def scaler_np(time_constant: float = 1.0, axis: str | None = None) -> AdaptiveStandardScalerTransformer:
@@ -1,7 +1,17 @@
1
+ """
2
+ Transpose or permute array dimensions.
3
+
4
+ .. note::
5
+ This module supports the :doc:`Array API standard </guides/explanations/array_api>`,
6
+ enabling use with NumPy, CuPy, PyTorch, and other compatible array libraries.
7
+ Memory layout optimization (C/F order) only applies to NumPy arrays.
8
+ """
9
+
1
10
  from types import EllipsisType
2
11
 
3
12
  import ezmsg.core as ez
4
13
  import numpy as np
14
+ from array_api_compat import get_namespace, is_numpy_array
5
15
  from ezmsg.baseproc import (
6
16
  BaseStatefulTransformer,
7
17
  BaseTransformerUnit,
@@ -84,6 +94,7 @@ class TransposeTransformer(BaseStatefulTransformer[TransposeSettings, AxisArray,
84
94
  return super().__call__(message)
85
95
 
86
96
  def _process(self, message: AxisArray) -> AxisArray:
97
+ xp = get_namespace(message.data)
87
98
  if self.state.axes_ints is None:
88
99
  # No transpose required
89
100
  if self.settings.order is None:
@@ -91,15 +102,19 @@ class TransposeTransformer(BaseStatefulTransformer[TransposeSettings, AxisArray,
91
102
  # Note: We should not be able to reach here because it should be shortcutted at passthrough.
92
103
  msg_out = message
93
104
  else:
94
- # If the memory is already contiguous in the correct order, np.require won't do anything.
95
- msg_out = replace(
96
- message,
97
- data=np.require(message.data, requirements=self.settings.order.upper()[0]),
98
- )
105
+ # Memory layout optimization only applies to numpy arrays
106
+ if is_numpy_array(message.data):
107
+ msg_out = replace(
108
+ message,
109
+ data=np.require(message.data, requirements=self.settings.order.upper()[0]),
110
+ )
111
+ else:
112
+ msg_out = message
99
113
  else:
100
114
  dims_out = [message.dims[ix] for ix in self.state.axes_ints]
101
- data_out = np.transpose(message.data, axes=self.state.axes_ints)
102
- if self.settings.order is not None:
115
+ data_out = xp.permute_dims(message.data, axes=self.state.axes_ints)
116
+ if self.settings.order is not None and is_numpy_array(data_out):
117
+ # Memory layout optimization only applies to numpy arrays
103
118
  data_out = np.require(data_out, requirements=self.settings.order.upper()[0])
104
119
  msg_out = replace(
105
120
  message,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ezmsg-sigproc
3
- Version: 2.7.0
3
+ Version: 2.9.0
4
4
  Summary: Timeseries signal processing implementations in ezmsg
5
5
  Author-email: Griffin Milsap <griffin.milsap@gmail.com>, Preston Peranich <pperanich@gmail.com>, Chadwick Boulay <chadwick.boulay@gmail.com>, Kyle McGraw <kmcgraw@blackrockneuro.com>
6
6
  License-Expression: MIT
@@ -1,55 +1,56 @@
1
1
  ezmsg/sigproc/__init__.py,sha256=8K4IcOA3-pfzadoM6s2Sfg5460KlJUocGgyTJTJl96U,52
2
- ezmsg/sigproc/__version__.py,sha256=egp6dAw7S80JrGAUNYGJNY3iqzeSzC4Vs5c3SnC3SIY,704
2
+ ezmsg/sigproc/__version__.py,sha256=24D27WSWcO2HaMUGCrVCG6Zbd76BrLDh2qQ1KbGX6Hc,704
3
3
  ezmsg/sigproc/activation.py,sha256=83vnTa3ZcC4Q3VSWcGfaqhCEqYRNySUOyVpMHZXfz-c,2755
4
4
  ezmsg/sigproc/adaptive_lattice_notch.py,sha256=ThUR48mbSHuThkimtD0j4IXNMrOVcpZgGhE7PCYfXhU,8818
5
- ezmsg/sigproc/affinetransform.py,sha256=umEsbEcuiCI02oljWueJnKaI0aJxJ0wqOjH5yXclEM0,8414
6
- ezmsg/sigproc/aggregate.py,sha256=yc3Hnak0yhucqlTCv9Isg6BKR24s6rMZqbZKpayyBgM,9048
5
+ ezmsg/sigproc/affinetransform.py,sha256=jl7DiSa5Yb0qsmFJbfSiSeGmvK1SGoBgycFC5JU5DVY,9434
6
+ ezmsg/sigproc/aggregate.py,sha256=7Hdz1m-S6Cl9h0oRQHeS_UTGBemhOB4XdFyX6cGcdHo,9362
7
7
  ezmsg/sigproc/bandpower.py,sha256=dAhH56sUrXNhcRFymTTwjdM_KcU5OxFzrR_sxIPAxyw,2264
8
8
  ezmsg/sigproc/base.py,sha256=SJvKEb8gw6mUMwlV5sH0iPG0bXrgS8tvkPwhI-j89MQ,3672
9
9
  ezmsg/sigproc/butterworthfilter.py,sha256=NKTGkgjvlmC1Dc9gD2Z6UBzUq12KicfnczrzM5ZTosk,5255
10
- ezmsg/sigproc/butterworthzerophase.py,sha256=QbJ_gGXrIfVOl_OybD07orO460owSMP5yTDMcGgASP0,4109
10
+ ezmsg/sigproc/butterworthzerophase.py,sha256=Df3F1QBBE39FBjNi67wvTsb1bSdTRTSTZXbZiKFlxC4,4105
11
11
  ezmsg/sigproc/cheby.py,sha256=B8pGt5_pOBpNZCmaibNl_NKkyuasd8ZEJXeTDCTaino,3711
12
12
  ezmsg/sigproc/combfilter.py,sha256=MSxr1I-jBePW_9AuCiv3RQ1HUNxIsNhLk0q1Iu8ikAw,4766
13
- ezmsg/sigproc/coordinatespaces.py,sha256=NWbQVvizmiU4F3AIwHHhiZ30Kg2IeSW0fRaa-yXkn-c,4610
13
+ ezmsg/sigproc/coordinatespaces.py,sha256=bp_0fTS9b27OQqLoFzgE3f9rb287P8y0S1dWWGrS08o,5298
14
14
  ezmsg/sigproc/decimate.py,sha256=DCX9p4ZrcGoQ9di-jmPKqiKERTkvTAtSqLg8chQLp84,2336
15
- ezmsg/sigproc/denormalize.py,sha256=CaH34QJ3OvsnhZT7bhCw2KVxVKvaU24ui6BS5-oe_x0,3040
16
- ezmsg/sigproc/detrend.py,sha256=TxqZ41AdjsH_yOTj8fBoKTnbKCbdyzVibpCHWkFARYU,898
17
- ezmsg/sigproc/diff.py,sha256=PC8KJAqaXMsdS_kPlDSEtXpcQ5rk92Ze_TeUjsVwbN0,2782
15
+ ezmsg/sigproc/denormalize.py,sha256=OACzoINCISNUmZcR2pqWfxi3p8uuBLCz1PMw4XNwOCs,3035
16
+ ezmsg/sigproc/detrend.py,sha256=2C-dhVy-ty4b22aE07kIF3VjqCoFitoZqyrX6hAjXYQ,894
17
+ ezmsg/sigproc/diff.py,sha256=nDf9-XywZKlfeainIeubl_NccmkuAdLjYMcWSPMquss,3179
18
18
  ezmsg/sigproc/downsample.py,sha256=Jqxt1Va1FrQLH1wUQpP0U79iQARTTHEKklKHy7yGL2o,3679
19
- ezmsg/sigproc/ewma.py,sha256=I6WZkf6yf8jnpLdeEAWfAcpui5erVcVMZo6hTMMvDvg,6247
19
+ ezmsg/sigproc/ewma.py,sha256=ecLq1ZlON85NWdW_5cz9chWirxjf4y9zCPMOt3IqQuk,7723
20
20
  ezmsg/sigproc/ewmfilter.py,sha256=9AuvVn3TDdf5R4bVolYNM46AtDVHFJa8MLGltY6mYPE,4795
21
- ezmsg/sigproc/extract_axis.py,sha256=4beqJ0EyUkT9Pa1FdYa9IteHGaxkh87j5_rYiZlvIG4,1598
21
+ ezmsg/sigproc/extract_axis.py,sha256=T2e9zW8N0bwOj4_zlB3I0oT0iwaBijpzF6wrFsCfD24,1593
22
22
  ezmsg/sigproc/fbcca.py,sha256=JYFWsMDRJEWwUNujr4EsFL5t1ux-cnBGamNVrCRO_RA,12043
23
- ezmsg/sigproc/filter.py,sha256=rLmJCgPevoODTL3g_D6czwVvcCD4uK8hJJ0tKfTH5_w,11609
23
+ ezmsg/sigproc/filter.py,sha256=IWg3J5IGVdv7kfp4zaKRzfjEuRa5xNzcq6fHtq4tPCM,11604
24
24
  ezmsg/sigproc/filterbank.py,sha256=tD7fn4dZzEvsmp_sSn16VVJ4WcJ5sN5lsSuNTLDCQZ8,13056
25
25
  ezmsg/sigproc/filterbankdesign.py,sha256=vLXQVJwoFEK4V6umqzcr1PJKcwv6pGO29klSWQXk7y0,4712
26
- ezmsg/sigproc/fir_hilbert.py,sha256=aurBCcpvsG69qD6Du2aHMye4HCBXF4RXzACdSxIy_Z8,10859
26
+ ezmsg/sigproc/fir_hilbert.py,sha256=ByZDsDFjbJx-EgLZF85vZCPQbprQaiMEuG2dyvTPiDY,10855
27
27
  ezmsg/sigproc/fir_pmc.py,sha256=Ze2pd9K8XrdDhgJZG2E7x-5C2hfd6kIns4bYjTJsBvc,6997
28
28
  ezmsg/sigproc/firfilter.py,sha256=7r_I476nYuixJsuwc_hQ0Fbq8WB0gnYBZUKs3zultOQ,3790
29
29
  ezmsg/sigproc/gaussiansmoothing.py,sha256=BfXm9YQoOtieM4ABK2KRgxeQz055rd7mqtTVqmjT3Rk,2672
30
30
  ezmsg/sigproc/kaiser.py,sha256=dIwgHbLXUHPtdotsGNLE9VG_clhcMgvVnSoFkMVgF9M,3483
31
+ ezmsg/sigproc/linear.py,sha256=b3NRzQNBvdU2jqenZT9XXFHax9Mavbj2xFiVxOwl1Ms,4662
31
32
  ezmsg/sigproc/messages.py,sha256=KQczHTeifn4BZycChN8ZcpfZoQW3lC_xuFmN72QT97s,925
32
33
  ezmsg/sigproc/quantize.py,sha256=uSM2z2xXwL0dgSltyzLEmlKjaJZ2meA3PDWX8_bM0Hs,2195
33
34
  ezmsg/sigproc/resample.py,sha256=3mm9pvxryNVhQuTCIMW3ToUkUfbVOCsIgvXUiurit1Y,11389
34
- ezmsg/sigproc/rollingscaler.py,sha256=j5hoWgJMbeDST7FXE6UBLSeR2hhfACIqQvFKr92ZTZA,8503
35
+ ezmsg/sigproc/rollingscaler.py,sha256=e-smSKDhmDD2nWIf6I77CtRxQp_7sHS268SGPi7aXp8,8499
35
36
  ezmsg/sigproc/sampler.py,sha256=iOk2YoUX22u9iTjFKimzP5V074RDBVcmswgfyxvZRZo,10761
36
- ezmsg/sigproc/scaler.py,sha256=n0aGo272vs_MnFRtT1b3vVCQOzY-UEAxnqZKlpIldX8,4041
37
+ ezmsg/sigproc/scaler.py,sha256=oBZa6uzyftChvk6aqBD5clil6pedx3IF-dptrb74EA0,5888
37
38
  ezmsg/sigproc/signalinjector.py,sha256=mB62H2b-ScgPtH1jajEpxgDHqdb-RKekQfgyNncsE8Y,2874
38
39
  ezmsg/sigproc/slicer.py,sha256=xLXxWf722V08ytVwvPimYjDKKj0pkC2HjdgCVaoaOvs,5195
39
40
  ezmsg/sigproc/spectral.py,sha256=wFzuihS7qJZMQcp0ds_qCG-zCbvh5DyhFRjn2wA9TWQ,322
40
41
  ezmsg/sigproc/spectrogram.py,sha256=g8xYWENzle6O5uEF-vfjsF5gOSDnJTwiu3ZudicO470,2893
41
42
  ezmsg/sigproc/spectrum.py,sha256=AAxrywIYpAPENRWvaaM2VjcKEaqMt6ra1E3Ao26jdZs,9523
42
- ezmsg/sigproc/transpose.py,sha256=Yx4losN7tKCacUt2GmFvjOSqJ0b3XhEepXzq_Z_iMCI,4381
43
+ ezmsg/sigproc/transpose.py,sha256=uRGieeYRFTO9gAhWzkq49f-Qo49rpDQhn9r3nLuwx4I,4983
43
44
  ezmsg/sigproc/wavelets.py,sha256=mN9TrZencyvKBfnuMiGZ_lzrE1O7DhVo05EYgCjXncg,7428
44
45
  ezmsg/sigproc/window.py,sha256=ZlawY4TPbLc46qIgcKhP4X7dpMDo4zNlnfzgV1eFaGU,15335
45
46
  ezmsg/sigproc/math/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
- ezmsg/sigproc/math/abs.py,sha256=_omQP4baHGu8MgKzNDMUsjrmA_yeXWNqKyTU7BiHCSE,741
47
- ezmsg/sigproc/math/add.py,sha256=lR4lB8-QdUfyARJcdf5sMQhbHtFpRNYt8_1zYzkVEhU,3659
48
- ezmsg/sigproc/math/clip.py,sha256=vJ8zpzVlTVPlkElsqyPVMOwyKBrgym8RD8BEOvf8g1o,1131
49
- ezmsg/sigproc/math/difference.py,sha256=TtAURz9TsW_Vk39sMJOT84P0xw0xDrgE3ToRi8MWVv4,4491
50
- ezmsg/sigproc/math/invert.py,sha256=SYJDNEb7snkVODnItfkkp8rhMrHEdyBmzg6qZkZLX2c,630
51
- ezmsg/sigproc/math/log.py,sha256=K8bZSm-ubTDY0oqwuc0IOMkdPIwpw1yysjTOG657kkQ,1488
52
- ezmsg/sigproc/math/scale.py,sha256=Zpzlz1NSLyvmggjs0pPypUKgXX99z2S3VXJ06fHok5M,942
47
+ ezmsg/sigproc/math/abs.py,sha256=Zrt0xhGnhf90Vk00S1PYkbeCOQshqkvh1HLj26N4AL0,979
48
+ ezmsg/sigproc/math/add.py,sha256=UikpS_vhaY58ssf46C1r4_tnRUFMl86xCmd5Y8GFZ1Q,3666
49
+ ezmsg/sigproc/math/clip.py,sha256=1D6mUlOzBB7L35G_KKYZmfg7nYlbuDdITV4EH0R-yUo,1529
50
+ ezmsg/sigproc/math/difference.py,sha256=uUYZgbLe-GrFSN6EOFjs9fQZllp827IluxL6m8TJuH8,4791
51
+ ezmsg/sigproc/math/invert.py,sha256=nz8jbfvDoez6s9NmAprBtTAI5oSDj0wNUPk8j13XiVk,855
52
+ ezmsg/sigproc/math/log.py,sha256=JhjSqLnQnvx_3F4txRYHuUPSJ12Yj2HvRTsCMNvlxpo,2022
53
+ ezmsg/sigproc/math/scale.py,sha256=4_xHcHNuf13E1fxIF5vbkPfkN4En6zkfPIKID7lCERk,1133
53
54
  ezmsg/sigproc/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
55
  ezmsg/sigproc/util/asio.py,sha256=aAj0e7OoBvkRy28k05HL2s9YPCTxOddc05xMN-qd4lQ,577
55
56
  ezmsg/sigproc/util/axisarray_buffer.py,sha256=TGDeC6CXmmp7OUuiGd6xYQijRGYDE4QGdWxjK5Vs3nE,14057
@@ -58,7 +59,7 @@ ezmsg/sigproc/util/message.py,sha256=ppN3IYtIAwrxWG9JOvgWFn1wDdIumkEzYFfqpH9VQkY
58
59
  ezmsg/sigproc/util/profile.py,sha256=eVOo9pXgusrnH1yfRdd2RsM7Dbe2UpyC0LJ9MfGpB08,416
59
60
  ezmsg/sigproc/util/sparse.py,sha256=NjbJitCtO0B6CENTlyd9c-lHEJwoCan-T3DIgPyeShw,4834
60
61
  ezmsg/sigproc/util/typeresolution.py,sha256=fMFzLi63dqCIclGFLcMdM870OYxJnkeWw6aWKNMk718,362
61
- ezmsg_sigproc-2.7.0.dist-info/METADATA,sha256=QtI9FWw5pFVEqs1Ma6tooyGj73dZ6IOnM0QueJnzeSY,1908
62
- ezmsg_sigproc-2.7.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
63
- ezmsg_sigproc-2.7.0.dist-info/licenses/LICENSE,sha256=seu0tKhhAMPCUgc1XpXGGaCxY1YaYvFJwqFuQZAl2go,1100
64
- ezmsg_sigproc-2.7.0.dist-info/RECORD,,
62
+ ezmsg_sigproc-2.9.0.dist-info/METADATA,sha256=H7aJdYvhCekfdihzdsPbvT1a942faGRIlcoeDd_23HI,1908
63
+ ezmsg_sigproc-2.9.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
64
+ ezmsg_sigproc-2.9.0.dist-info/licenses/LICENSE,sha256=seu0tKhhAMPCUgc1XpXGGaCxY1YaYvFJwqFuQZAl2go,1100
65
+ ezmsg_sigproc-2.9.0.dist-info/RECORD,,