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.
- ezmsg/sigproc/__version__.py +2 -2
- ezmsg/sigproc/affinetransform.py +23 -1
- ezmsg/sigproc/aggregate.py +9 -0
- ezmsg/sigproc/butterworthzerophase.py +1 -1
- ezmsg/sigproc/coordinatespaces.py +22 -5
- ezmsg/sigproc/denormalize.py +3 -4
- ezmsg/sigproc/detrend.py +1 -1
- ezmsg/sigproc/diff.py +18 -8
- ezmsg/sigproc/ewma.py +44 -7
- ezmsg/sigproc/extract_axis.py +1 -2
- ezmsg/sigproc/filter.py +3 -4
- ezmsg/sigproc/fir_hilbert.py +1 -1
- ezmsg/sigproc/linear.py +120 -0
- ezmsg/sigproc/math/abs.py +11 -6
- ezmsg/sigproc/math/add.py +1 -2
- ezmsg/sigproc/math/clip.py +22 -17
- ezmsg/sigproc/math/difference.py +9 -3
- ezmsg/sigproc/math/invert.py +8 -3
- ezmsg/sigproc/math/log.py +19 -8
- ezmsg/sigproc/math/scale.py +8 -3
- ezmsg/sigproc/rollingscaler.py +4 -4
- ezmsg/sigproc/scaler.py +52 -3
- ezmsg/sigproc/transpose.py +22 -7
- {ezmsg_sigproc-2.7.0.dist-info → ezmsg_sigproc-2.9.0.dist-info}/METADATA +1 -1
- {ezmsg_sigproc-2.7.0.dist-info → ezmsg_sigproc-2.9.0.dist-info}/RECORD +27 -26
- {ezmsg_sigproc-2.7.0.dist-info → ezmsg_sigproc-2.9.0.dist-info}/WHEEL +0 -0
- {ezmsg_sigproc-2.7.0.dist-info → ezmsg_sigproc-2.9.0.dist-info}/licenses/LICENSE +0 -0
ezmsg/sigproc/__version__.py
CHANGED
|
@@ -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.
|
|
32
|
-
__version_tuple__ = version_tuple = (2,
|
|
31
|
+
__version__ = version = '2.9.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (2, 9, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
ezmsg/sigproc/affinetransform.py
CHANGED
|
@@ -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 (
|
ezmsg/sigproc/aggregate.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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:
|
ezmsg/sigproc/denormalize.py
CHANGED
|
@@ -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.
|
|
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
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
|
|
5
|
-
from ezmsg.
|
|
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 =
|
|
47
|
-
|
|
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
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
ezmsg/sigproc/extract_axis.py
CHANGED
|
@@ -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.
|
|
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
|
ezmsg/sigproc/fir_hilbert.py
CHANGED
|
@@ -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,
|
ezmsg/sigproc/linear.py
ADDED
|
@@ -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
|
-
"""
|
|
2
|
-
|
|
1
|
+
"""
|
|
2
|
+
Take the absolute value of the data.
|
|
3
3
|
|
|
4
|
-
|
|
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
|
-
|
|
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
|
|
ezmsg/sigproc/math/clip.py
CHANGED
|
@@ -1,27 +1,32 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14
|
-
"""Lower clip bound."""
|
|
17
|
+
min: float | None = None
|
|
18
|
+
"""Lower clip bound. If None, no lower clipping is applied."""
|
|
15
19
|
|
|
16
|
-
|
|
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=
|
|
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(
|
|
37
|
+
def clip(min: float | None = None, max: float | None = None) -> ClipTransformer:
|
|
33
38
|
"""
|
|
34
|
-
Clips the data to be within the specified range.
|
|
39
|
+
Clips the data to be within the specified range.
|
|
35
40
|
|
|
36
41
|
Args:
|
|
37
|
-
|
|
38
|
-
|
|
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(
|
|
48
|
+
return ClipTransformer(ClipSettings(min=min, max=max))
|
ezmsg/sigproc/math/difference.py
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
"""
|
|
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
|
ezmsg/sigproc/math/invert.py
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
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]):
|
ezmsg/sigproc/math/scale.py
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
"""
|
|
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
|
ezmsg/sigproc/rollingscaler.py
CHANGED
|
@@ -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.
|
|
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(
|
|
86
|
-
|
|
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:
|
ezmsg/sigproc/transpose.py
CHANGED
|
@@ -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
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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 =
|
|
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.
|
|
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=
|
|
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=
|
|
6
|
-
ezmsg/sigproc/aggregate.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
16
|
-
ezmsg/sigproc/detrend.py,sha256=
|
|
17
|
-
ezmsg/sigproc/diff.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
47
|
-
ezmsg/sigproc/math/add.py,sha256=
|
|
48
|
-
ezmsg/sigproc/math/clip.py,sha256=
|
|
49
|
-
ezmsg/sigproc/math/difference.py,sha256=
|
|
50
|
-
ezmsg/sigproc/math/invert.py,sha256=
|
|
51
|
-
ezmsg/sigproc/math/log.py,sha256=
|
|
52
|
-
ezmsg/sigproc/math/scale.py,sha256=
|
|
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.
|
|
62
|
-
ezmsg_sigproc-2.
|
|
63
|
-
ezmsg_sigproc-2.
|
|
64
|
-
ezmsg_sigproc-2.
|
|
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,,
|
|
File without changes
|
|
File without changes
|