ezmsg-sigproc 1.3.3__py3-none-any.whl → 1.4.1__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.
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.3.3'
16
- __version_tuple__ = version_tuple = (1, 3, 3)
15
+ __version__ = version = '1.4.1'
16
+ __version_tuple__ = version_tuple = (1, 4, 1)
@@ -43,6 +43,18 @@ ACTIVATIONS = {
43
43
  def activation(
44
44
  function: typing.Union[str, ActivationFunction],
45
45
  ) -> typing.Generator[AxisArray, AxisArray, None]:
46
+ """
47
+ Transform the data with a simple activation function.
48
+
49
+ Args:
50
+ function: An enum value from ActivationFunction or a string representing the activation function.
51
+ Possible values are: SIGMOID, EXPIT, LOGIT, LOGEXPIT, "sigmoid", "expit", "logit", "log_expit".
52
+ SIGMOID and EXPIT are equivalent. See :obj:`scipy.special.expit` for more details.
53
+
54
+ Returns: A primed generator that, when passed an input message via `.send(msg)`, yields an AxisArray
55
+ with the data payload containing a transformed version of the input data.
56
+
57
+ """
46
58
  if type(function) is ActivationFunction:
47
59
  func = ACTIVATIONS[function]
48
60
  else:
@@ -24,7 +24,7 @@ def affine_transform(
24
24
  Args:
25
25
  weights: An array of weights or a path to a file with weights compatible with np.loadtxt.
26
26
  axis: The name of the axis to apply the transformation to. Defaults to the leading (0th) axis in the array.
27
- right_multiply: Set False to tranpose the weights before applying.
27
+ right_multiply: Set False to transpose the weights before applying.
28
28
 
29
29
  Returns:
30
30
  A primed generator object that yields an :obj:`AxisArray` object for every
@@ -20,11 +20,13 @@ class AggregationFunction(OptionsEnum):
20
20
  MEAN = "mean"
21
21
  MEDIAN = "median"
22
22
  STD = "std"
23
+ SUM = "sum"
23
24
  NANMAX = "nanmax"
24
25
  NANMIN = "nanmin"
25
26
  NANMEAN = "nanmean"
26
27
  NANMEDIAN = "nanmedian"
27
28
  NANSTD = "nanstd"
29
+ NANSUM = "nansum"
28
30
  ARGMIN = "argmin"
29
31
  ARGMAX = "argmax"
30
32
 
@@ -36,11 +38,13 @@ AGGREGATORS = {
36
38
  AggregationFunction.MEAN: np.mean,
37
39
  AggregationFunction.MEDIAN: np.median,
38
40
  AggregationFunction.STD: np.std,
41
+ AggregationFunction.SUM: np.sum,
39
42
  AggregationFunction.NANMAX: np.nanmax,
40
43
  AggregationFunction.NANMIN: np.nanmin,
41
44
  AggregationFunction.NANMEAN: np.nanmean,
42
45
  AggregationFunction.NANMEDIAN: np.nanmedian,
43
46
  AggregationFunction.NANSTD: np.nanstd,
47
+ AggregationFunction.NANSUM: np.nansum,
44
48
  AggregationFunction.ARGMIN: np.argmin,
45
49
  AggregationFunction.ARGMAX: np.argmax,
46
50
  }
@@ -62,7 +66,7 @@ def ranged_aggregate(
62
66
  operation: :obj:`AggregationFunction` to apply to each band.
63
67
 
64
68
  Returns:
65
- A primed generator object ready to yield an AxisArray for each .send(axis_array)
69
+ A primed generator object ready to yield an :obj:`AxisArray` for each .send(axis_array)
66
70
  """
67
71
  msg_out = AxisArray(np.array([]), dims=[""])
68
72
 
@@ -27,7 +27,8 @@ def bandpower(
27
27
  bands: (min, max) tuples of band limits in Hz.
28
28
 
29
29
  Returns:
30
- A primed generator object ready to yield an AxisArray for each .send(axis_array)
30
+ A primed generator object ready to yield an :obj:`AxisArray` for each .send(axis_array)
31
+ with the data payload being the average spectral power in each band of the input data.
31
32
  """
32
33
  msg_out = AxisArray(np.array([]), dims=[""])
33
34
 
ezmsg/sigproc/base.py CHANGED
@@ -1,3 +1,4 @@
1
+ import math
1
2
  import traceback
2
3
  import typing
3
4
 
@@ -30,7 +31,7 @@ class GenAxisArray(ez.Unit):
30
31
  async def on_signal(self, message: AxisArray) -> typing.AsyncGenerator:
31
32
  try:
32
33
  ret = self.STATE.gen.send(message)
33
- if ret.data.size > 0:
34
+ if math.prod(ret.data.shape) > 0:
34
35
  yield self.OUTPUT_SIGNAL, ret
35
36
  except (StopIteration, GeneratorExit):
36
37
  ez.logger.debug(f"Generator closed in {self.address}")
@@ -13,19 +13,22 @@ class ButterworthFilterSettings(FilterSettingsBase):
13
13
  """Settings for :obj:`ButterworthFilter`."""
14
14
 
15
15
  order: int = 0
16
+ """
17
+ Filter order
18
+ """
16
19
 
17
20
  cuton: typing.Optional[float] = None
18
21
  """
19
- Cuton frequency (Hz). If cutoff is not specified then this is the highpass corner, otherwise
20
- if it is lower than cutoff then this is the beginning of the bandpass
21
- or if it is greater than cuton then it is the end of the bandstop.
22
+ Cuton frequency (Hz). If `cutoff` is not specified then this is the highpass corner. Otherwise,
23
+ if this is lower than `cutoff` then this is the beginning of the bandpass
24
+ or if this is greater than `cutoff` then this is the end of the bandstop.
22
25
  """
23
26
 
24
27
  cutoff: typing.Optional[float] = None
25
28
  """
26
- Cutoff frequency (Hz). If cuton is not specified then this is the lowpass corner, otherwise
27
- if it is greater than cuton then this is the end of the bandpass,
28
- or if it is less than cuton then it is the beginning of the bandstop.
29
+ Cutoff frequency (Hz). If `cuton` is not specified then this is the lowpass corner. Otherwise,
30
+ if this is greater than `cuton` then this is the end of the bandpass,
31
+ or if this is less than `cuton` then this is the beginning of the bandstop.
29
32
  """
30
33
 
31
34
  def filter_specs(
@@ -76,7 +79,8 @@ def butter(
76
79
  coef_type: "ba" or "sos"
77
80
 
78
81
  Returns:
79
- A primed generator object which accepts .send(axis_array) and yields filtered axis array.
82
+ A primed generator object which accepts an :obj:`AxisArray` via .send(axis_array)
83
+ and yields an :obj:`AxisArray` with filtered data.
80
84
 
81
85
  """
82
86
  # IO
@@ -25,10 +25,10 @@ def downsample(
25
25
  factor: Downsampling factor.
26
26
 
27
27
  Returns:
28
- A primed generator object ready to receive a `.send(axis_array)`
29
- and yields the downsampled data.
28
+ A primed generator object ready to receive an :obj:`AxisArray` via `.send(axis_array)`
29
+ and yields an :obj:`AxisArray` with its data downsampled.
30
30
  Note that if a send chunk does not have sufficient samples to reach the
31
- next downsample interval then `None` is yielded.
31
+ next downsample interval then an :obj:`AxisArray` with size-zero data is yielded.
32
32
 
33
33
  """
34
34
  msg_out = AxisArray(np.array([]), dims=[""])
ezmsg/sigproc/filter.py CHANGED
@@ -38,7 +38,7 @@ def filtergen(
38
38
  axis: str, coefs: typing.Optional[typing.Tuple[np.ndarray]], coef_type: str
39
39
  ) -> typing.Generator[AxisArray, AxisArray, None]:
40
40
  """
41
- Construct a generic filter generator function.
41
+ Filter data using the provided coefficients.
42
42
 
43
43
  Args:
44
44
  axis: The name of the axis to operate on.
@@ -46,7 +46,8 @@ def filtergen(
46
46
  coef_type: The type of filter coefficients. One of "ba" or "sos".
47
47
 
48
48
  Returns:
49
- A generator that expects .send(axis_array) and yields the filtered :obj:`AxisArray`.
49
+ A primed generator that, when passed an :obj:`AxisArray` via `.send(axis_array)`,
50
+ yields an :obj:`AxisArray` with the data filtered.
50
51
  """
51
52
  # Massage inputs
52
53
  if coefs is not None and not isinstance(coefs, tuple):
@@ -97,7 +98,10 @@ def filtergen(
97
98
  n_tile = (1,) + n_tile
98
99
  zi = np.tile(zi[zi_expand], n_tile)
99
100
 
100
- dat_out, zi = filt_func(*coefs, msg_in.data, axis=axis_idx, zi=zi)
101
+ if msg_in.data.size > 0:
102
+ dat_out, zi = filt_func(*coefs, msg_in.data, axis=axis_idx, zi=zi)
103
+ else:
104
+ dat_out = msg_in.data
101
105
  msg_out = replace(msg_in, data=dat_out)
102
106
 
103
107
 
@@ -43,9 +43,9 @@ def filterbank(
43
43
  new_axis: str = "kernel",
44
44
  ) -> typing.Generator[AxisArray, AxisArray, None]:
45
45
  """
46
- Returns a generator that perform multiple (direct or fft) convolutions on a signal using a bank of kernels.
47
- This generator is intended to be used during online processing, therefore both direct and fft convolutions
48
- use the overlap-add method.
46
+ Perform multiple (direct or fft) convolutions on a signal using a bank of kernels.
47
+ This is intended to be used during online processing, therefore both direct and fft convolutions
48
+ use the overlap-add method.
49
49
  Args:
50
50
  kernels:
51
51
  mode: "conv", "fft", or "auto". If "auto", the mode is determined by the size of the input data.
@@ -59,7 +59,8 @@ def filterbank(
59
59
  axis: The name of the axis to operate on. This should usually be "time".
60
60
  new_axis: The name of the new axis corresponding to the kernel index.
61
61
 
62
- Returns:
62
+ Returns: A primed generator that, when passed an input message via `.send(msg)`, yields an :obj:`AxisArray`
63
+ with the data payload containing the absolute value of the input :obj:`AxisArray` data.
63
64
 
64
65
  """
65
66
  msg_out: typing.Optional[AxisArray] = None
@@ -133,6 +134,7 @@ def filterbank(
133
134
  + msg_in.dims[targ_ax_ix + 1 :]
134
135
  + [new_axis, axis],
135
136
  axes=msg_in.axes.copy(), # We do not have info for kernel/filter axis :(.
137
+ key=msg_in.key,
136
138
  )
137
139
 
138
140
  # Determine optimal mode. Assumes 100 msec chunks.
ezmsg/sigproc/math/abs.py CHANGED
@@ -11,9 +11,15 @@ from ..base import GenAxisArray
11
11
 
12
12
  @consumer
13
13
  def abs() -> typing.Generator[AxisArray, AxisArray, None]:
14
+ """
15
+ Take the absolute value of the data. See :obj:`np.abs` for more details.
16
+
17
+ Returns: A primed generator that, when passed an input message via `.send(msg)`, yields an :obj:`AxisArray`
18
+ with the data payload containing the absolute value of the input :obj:`AxisArray` data.
19
+ """
14
20
  msg_out = AxisArray(np.array([]), dims=[""])
15
21
  while True:
16
- msg_in = yield msg_out
22
+ msg_in: AxisArray = yield msg_out
17
23
  msg_out = replace(msg_in, data=np.abs(msg_in.data))
18
24
 
19
25
 
@@ -11,10 +11,20 @@ from ..base import GenAxisArray
11
11
 
12
12
  @consumer
13
13
  def clip(a_min: float, a_max: float) -> typing.Generator[AxisArray, AxisArray, None]:
14
- msg_in = AxisArray(np.array([]), dims=[""])
14
+ """
15
+ Clips the data to be within the specified range. See :obj:`np.clip` for more details.
16
+
17
+ Args:
18
+ a_min: Lower clip bound
19
+ a_max: Upper clip bound
20
+
21
+ Returns: A primed generator that, when passed an input message via `.send(msg)`, yields an :obj:`AxisArray`
22
+ with the data payload containing the clipped version of the input :obj:`AxisArray` data.
23
+
24
+ """
15
25
  msg_out = AxisArray(np.array([]), dims=[""])
16
26
  while True:
17
- msg_in = yield msg_out
27
+ msg_in: AxisArray = yield msg_out
18
28
  msg_out = replace(msg_in, data=np.clip(msg_in.data, a_min, a_max))
19
29
 
20
30
 
@@ -16,6 +16,15 @@ def const_difference(
16
16
  """
17
17
  result = (in_data - value) if subtrahend else (value - in_data)
18
18
  https://en.wikipedia.org/wiki/Template:Arithmetic_operations
19
+
20
+ Args:
21
+ value: number to subtract or be subtracted from the input data
22
+ subtrahend: If True (default) then value is subtracted from the input data.
23
+ If False, the input data is subtracted from value.
24
+
25
+ Returns: A primed generator that, when passed an input message via `.send(msg)`, yields an :obj:`AxisArray`
26
+ with the data payload containing the difference between the input :obj:`AxisArray` data and the value.
27
+
19
28
  """
20
29
  msg_out = AxisArray(np.array([]), dims=[""])
21
30
  while True:
@@ -11,10 +11,16 @@ from ..base import GenAxisArray
11
11
 
12
12
  @consumer
13
13
  def invert() -> typing.Generator[AxisArray, AxisArray, None]:
14
- msg_in = AxisArray(np.array([]), dims=[""])
14
+ """
15
+ Take the inverse of the data.
16
+
17
+ Returns: A primed generator that, when passed an input message via `.send(msg)`, yields an :obj:`AxisArray`
18
+ with the data payload containing the inversion of the input :obj:`AxisArray` data.
19
+
20
+ """
15
21
  msg_out = AxisArray(np.array([]), dims=[""])
16
22
  while True:
17
- msg_in = yield msg_out
23
+ msg_in: AxisArray = yield msg_out
18
24
  msg_out = replace(msg_in, data=1 / msg_in.data)
19
25
 
20
26
 
ezmsg/sigproc/math/log.py CHANGED
@@ -12,12 +12,31 @@ from ..base import GenAxisArray
12
12
  @consumer
13
13
  def log(
14
14
  base: float = 10.0,
15
+ clip_zero: bool = False,
15
16
  ) -> typing.Generator[AxisArray, AxisArray, None]:
16
- msg_in = AxisArray(np.array([]), dims=[""])
17
+ """
18
+ Take the logarithm of the data. See :obj:`np.log` for more details.
19
+
20
+ Args:
21
+ base: The base of the logarithm. Default is 10.
22
+ clip_zero: If True, clip the data to the minimum positive value of the data type before taking the log.
23
+
24
+ Returns: A primed generator that, when passed an input message via `.send(msg)`, yields an :obj:`AxisArray`
25
+ with the data payload containing the logarithm of the input :obj:`AxisArray` data.
26
+
27
+ """
17
28
  msg_out = AxisArray(np.array([]), dims=[""])
18
29
  log_base = np.log(base)
19
30
  while True:
20
- msg_in = yield msg_out
31
+ msg_in: AxisArray = yield msg_out
32
+ if (
33
+ clip_zero
34
+ and np.any(msg_in.data <= 0)
35
+ and np.issubdtype(msg_in.data.dtype, np.floating)
36
+ ):
37
+ msg_in.data = np.clip(
38
+ msg_in.data, a_min=np.finfo(msg_in.data.dtype).tiny, a_max=None
39
+ )
21
40
  msg_out = replace(msg_in, data=np.log(msg_in.data) / log_base)
22
41
 
23
42
 
@@ -11,10 +11,19 @@ from ..base import GenAxisArray
11
11
 
12
12
  @consumer
13
13
  def scale(scale: float = 1.0) -> typing.Generator[AxisArray, AxisArray, None]:
14
- msg_in = AxisArray(np.array([]), dims=[""])
14
+ """
15
+ Scale the data by a constant factor.
16
+
17
+ Args:
18
+ scale: Factor by which to scale the data magnitude.
19
+
20
+ Returns: A primed generator that, when passed an input message via `.send(msg)`, yields an :obj:`AxisArray`
21
+ with the data payload containing the input :obj:`AxisArray` data scaled by a constant factor.
22
+
23
+ """
15
24
  msg_out = AxisArray(np.array([]), dims=[""])
16
25
  while True:
17
- msg_in = yield msg_out
26
+ msg_in: AxisArray = yield msg_out
18
27
  msg_out = replace(msg_in, data=scale * msg_in.data)
19
28
 
20
29
 
ezmsg/sigproc/sampler.py CHANGED
@@ -43,7 +43,7 @@ def sampler(
43
43
  typing.List[SampleMessage], typing.Union[AxisArray, SampleTriggerMessage], None
44
44
  ]:
45
45
  """
46
- A generator function that samples data into a buffer, accepts triggers, and returns slices of sampled
46
+ Sample data into a buffer, accept triggers, and return slices of sampled
47
47
  data around the trigger time.
48
48
 
49
49
  Args:
ezmsg/sigproc/scaler.py CHANGED
@@ -33,8 +33,7 @@ def scaler(
33
33
  time_constant: float = 1.0, axis: typing.Optional[str] = None
34
34
  ) -> typing.Generator[AxisArray, AxisArray, None]:
35
35
  """
36
- Create a generator function that applies the
37
- adaptive standard scaler from https://riverml.xyz/latest/api/preprocessing/AdaptiveStandardScaler/
36
+ Apply the adaptive standard scaler from https://riverml.xyz/latest/api/preprocessing/AdaptiveStandardScaler/
38
37
  This is faster than :obj:`scaler_np` for single-channel data.
39
38
 
40
39
  Args:
@@ -42,8 +41,8 @@ def scaler(
42
41
  axis: The name of the axis to accumulate statistics over.
43
42
 
44
43
  Returns:
45
- A primed generator object that expects `.send(axis_array)` and yields a
46
- standardized, or "Z-scored" version of the input.
44
+ A primed generator object that expects to be sent a :obj:`AxisArray` via `.send(axis_array)`
45
+ and yields an :obj:`AxisArray` with its data being a standardized, or "Z-scored" version of the input data.
47
46
  """
48
47
  from river import preprocessing
49
48
 
@@ -90,8 +89,8 @@ def scaler_np(
90
89
  axis: The name of the axis to accumulate statistics over.
91
90
 
92
91
  Returns:
93
- A primed generator object that expects `.send(axis_array)` and yields a
94
- standardized, or "Z-scored" version of the input.
92
+ A primed generator object that expects to be sent a :obj:`AxisArray` via `.send(axis_array)`
93
+ and yields an :obj:`AxisArray` with its data being a standardized, or "Z-scored" version of the input data.
95
94
  """
96
95
  msg_out = AxisArray(np.array([]), dims=[""])
97
96
 
@@ -22,6 +22,11 @@ class SignalInjectorState(ez.State):
22
22
 
23
23
 
24
24
  class SignalInjector(ez.Unit):
25
+ """
26
+ Add a sinusoidal signal to the input signal. Each feature gets a different amplitude of the sinusoid.
27
+ All features get the same frequency sinusoid. The frequency and base amplitude can be changed while running.
28
+ """
29
+
25
30
  SETTINGS = SignalInjectorSettings
26
31
  STATE = SignalInjectorState
27
32
 
ezmsg/sigproc/slicer.py CHANGED
@@ -48,6 +48,18 @@ def parse_slice(s: str) -> typing.Tuple[typing.Union[slice, int], ...]:
48
48
  def slicer(
49
49
  selection: str = "", axis: typing.Optional[str] = None
50
50
  ) -> typing.Generator[AxisArray, AxisArray, None]:
51
+ """
52
+ Slice along a particular axis.
53
+
54
+ Args:
55
+ selection: See :obj:`ezmsg.sigproc.slicer.parse_slice` for details.
56
+ axis: The name of the axis to slice along. If None, the last axis is used.
57
+
58
+ Returns:
59
+ A primed generator object ready to yield an :obj:`AxisArray` for each .send(axis_array)
60
+ with the data payload containing a sliced view of the input data.
61
+
62
+ """
51
63
  msg_out = AxisArray(np.array([]), dims=[""])
52
64
 
53
65
  # State variables
@@ -98,7 +110,8 @@ def slicer(
98
110
  and hasattr(msg_in.axes[axis], "labels")
99
111
  and len(msg_in.axes[axis].labels) > 0
100
112
  ):
101
- new_labels = msg_in.axes[axis].labels[_slice]
113
+ in_labels = np.array(msg_in.axes[axis].labels)
114
+ new_labels = in_labels[_slice].tolist()
102
115
  new_axis = replace(msg_in.axes[axis], labels=new_labels)
103
116
 
104
117
  replace_kwargs = {}
@@ -33,13 +33,17 @@ def spectrogram(
33
33
  output: See :obj:`ezmsg.sigproc.spectrum.spectrum`
34
34
 
35
35
  Returns:
36
- A primed generator object that expects `.send(axis_array)` of continuous data
37
- and yields an AxisArray of time-frequency power values.
36
+ A primed generator object that expects an :obj:`AxisArray` via `.send(axis_array)`
37
+ with continuous data in its .data payload, and yields an :obj:`AxisArray` of time-frequency power values.
38
38
  """
39
39
 
40
40
  pipeline = compose(
41
41
  windowing(
42
- axis="time", newaxis="win", window_dur=window_dur, window_shift=window_shift
42
+ axis="time",
43
+ newaxis="win",
44
+ window_dur=window_dur,
45
+ window_shift=window_shift,
46
+ zero_pad_until="shift" if window_shift is not None else "input",
43
47
  ),
44
48
  spectrum(axis="time", window=window, transform=transform, output=output),
45
49
  modify_axis(name_map={"win": "time"}),
ezmsg/sigproc/spectrum.py CHANGED
@@ -92,8 +92,8 @@ def spectrum(
92
92
  nfft: The number of points to use for the FFT. If None, the length of the input data is used.
93
93
 
94
94
  Returns:
95
- A primed generator object that expects `.send(axis_array)` of continuous data
96
- and yields an AxisArray of spectral magnitudes or powers.
95
+ A primed generator object that expects an :obj:`AxisArray` via `.send(axis_array)` containing continuous data
96
+ and yields an :obj:`AxisArray` with data of spectral magnitudes or powers.
97
97
  """
98
98
  msg_out = AxisArray(np.array([]), dims=[""])
99
99
 
ezmsg/sigproc/wavelets.py CHANGED
@@ -20,8 +20,8 @@ def cwt(
20
20
  axis: str = "time",
21
21
  ) -> typing.Generator[AxisArray, AxisArray, None]:
22
22
  """
23
- Build a generator to perform a continuous wavelet transform on sent AxisArray messages.
24
- The function is equivalent to the `pywt.cwt` function, but is designed to work with streaming data.
23
+ Perform a continuous wavelet transform.
24
+ The function is equivalent to the :obj:`pywt.cwt` function, but is designed to work with streaming data.
25
25
 
26
26
  Args:
27
27
  scales: The wavelet scales to use.
@@ -31,7 +31,8 @@ def cwt(
31
31
  because fft and matrix multiplication is much faster on the last axis.
32
32
 
33
33
  Returns:
34
- A Generator object that expects `.send(axis_array)` of continuous data
34
+ A primed Generator object that expects an :obj:`AxisArray` via `.send(axis_array)` of continuous data
35
+ and yields an :obj:`AxisArray` with a continuous wavelet transform in its data.
35
36
  """
36
37
  msg_out: typing.Optional[AxisArray] = None
37
38
 
@@ -114,6 +115,7 @@ def cwt(
114
115
  **msg_in.axes,
115
116
  "freq": AxisArray.Axis("Hz", offset=freqs[0], gain=fstep),
116
117
  },
118
+ key=msg_in.key,
117
119
  )
118
120
  last_conv_samp = np.zeros(
119
121
  dummy_shape[:-1] + (1,), dtype=template.data.dtype
ezmsg/sigproc/window.py CHANGED
@@ -24,7 +24,9 @@ def windowing(
24
24
  zero_pad_until: str = "input",
25
25
  ) -> typing.Generator[AxisArray, AxisArray, None]:
26
26
  """
27
- Construct a generator that yields windows of data from an input :obj:`AxisArray`.
27
+ Apply a sliding window along the specified axis to input streaming data.
28
+ The `windowing` method is perhaps the most useful and versatile method in ezmsg.sigproc, but its parameterization
29
+ can be difficult. Please read the argument descriptions carefully.
28
30
 
29
31
  Args:
30
32
  axis: The axis along which to segment windows.
@@ -48,8 +50,8 @@ def windowing(
48
50
  - "none" does not pad the buffer. No outputs will be yielded until at least `window_dur` data has been seen.
49
51
 
50
52
  Returns:
51
- A (primed) generator that accepts .send(an AxisArray object) and yields a list of windowed
52
- AxisArray objects. The list will always be length-1 if `newaxis` is not None or `window_shift` is None.
53
+ A primed generator that accepts an :obj:`AxisArray` via `.send(axis_array)`
54
+ and yields an :obj:`AxisArray` with the data payload containing a windowed version of the input data.
53
55
  """
54
56
  # Check arguments
55
57
  if newaxis is None:
@@ -77,7 +79,7 @@ def windowing(
77
79
  b_1to1 = window_shift is None
78
80
  newaxis_warned: bool = b_1to1
79
81
  out_newaxis: typing.Optional[AxisArray.Axis] = None
80
- out_dims: typing.typing.Optional[typing.List[str]] = None
82
+ out_dims: typing.Optional[typing.List[str]] = None
81
83
 
82
84
  check_inputs = {"samp_shape": None, "fs": None, "key": None}
83
85
 
@@ -123,11 +125,12 @@ def windowing(
123
125
  else: # i.e. zero_pad_until == "input"
124
126
  req_samples = msg_in.data.shape[axis_idx]
125
127
  n_zero = max(0, window_samples - req_samples)
126
- buffer = np.zeros(
128
+ init_buffer_shape = (
127
129
  msg_in.data.shape[:axis_idx]
128
130
  + (n_zero,)
129
131
  + msg_in.data.shape[axis_idx + 1 :]
130
132
  )
133
+ buffer = np.zeros(init_buffer_shape, dtype=msg_in.data.dtype)
131
134
 
132
135
  # Add new data to buffer.
133
136
  # Currently, we concatenate the new time samples and clip the output.
@@ -174,10 +177,14 @@ def windowing(
174
177
  if b_1to1:
175
178
  # one-to-one mode -- Each send yields exactly one window containing only the most recent samples.
176
179
  buffer = slice_along_axis(buffer, slice(-window_samples, None), axis_idx)
177
- out_dat = np.expand_dims(buffer, axis=axis_idx)
180
+ out_dat = buffer.reshape(
181
+ buffer.shape[:axis_idx] + (1,) + buffer.shape[axis_idx:]
182
+ )
178
183
  out_newaxis = replace(out_newaxis, offset=buffer_offset[-window_samples])
179
184
  elif buffer.shape[axis_idx] >= window_samples:
180
185
  # Deterministic window shifts.
186
+ # Note: After https://github.com/ezmsg-org/ezmsg/pull/152, add `window_shift_samples` as the last arg
187
+ # to `sliding_win_oneaxis` and remove the call to `slice_along_axis`.
181
188
  out_dat = sliding_win_oneaxis(buffer, window_samples, axis_idx)
182
189
  out_dat = slice_along_axis(
183
190
  out_dat, slice(None, None, window_shift_samples), axis_idx
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ezmsg-sigproc
3
- Version: 1.3.3
3
+ Version: 1.4.1
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>
6
6
  License-Expression: MIT
@@ -0,0 +1,35 @@
1
+ ezmsg/sigproc/__init__.py,sha256=8K4IcOA3-pfzadoM6s2Sfg5460KlJUocGgyTJTJl96U,52
2
+ ezmsg/sigproc/__version__.py,sha256=oFZsPxoSsCY6D2DiWMSueNvMDRRQN5ssWrPdQtlLJ_o,411
3
+ ezmsg/sigproc/activation.py,sha256=amvVjO-1BcChgK0VQi44i7-Tmz_2FK1xb-BTC52_DO0,2642
4
+ ezmsg/sigproc/affinetransform.py,sha256=a5xt6LSymlV3gNSdCjWdtUoi9kOkWiYx6oeiGEIphYI,8802
5
+ ezmsg/sigproc/aggregate.py,sha256=Wnvz1xXDQhdVkTfVPOmhdmjgIhR_YQhioo8Dwh4BaoE,5615
6
+ ezmsg/sigproc/bandpower.py,sha256=QPktf3v7sojDHhwUVcK3UNPU-Dm4sf2QQw3qmYWZ1r0,2228
7
+ ezmsg/sigproc/base.py,sha256=vut0BLjgc0mxYRbs7tDd9XzwRFA2_GcsgXZmIYovR0Y,1248
8
+ ezmsg/sigproc/butterworthfilter.py,sha256=vTESZAAcQhWLI8yIMOApsGAYgjv2ekCZsTIliFjEkc8,5345
9
+ ezmsg/sigproc/decimate.py,sha256=zNxJXauE4Z54Yy7tpN9_oJuW0I0ppQRWhSpwb3Ri2gc,1473
10
+ ezmsg/sigproc/downsample.py,sha256=0TOhWkg7B9g3FDcewHINZiNuo28JyXRtgTYvZ7C8ImM,3384
11
+ ezmsg/sigproc/ewmfilter.py,sha256=XzTmouRa7AnPjTiI32-oumwm4ZfOl6uPPUSldExNpAs,4516
12
+ ezmsg/sigproc/filter.py,sha256=x3ytFowxg4O0QFQeMDFmQieV86H-6Q3Mkt3ThRIOh60,8301
13
+ ezmsg/sigproc/filterbank.py,sha256=Rhs7AKWy36lrMlzftEbYPXPW-FwZZ-QUCEp_QFG9mFU,12443
14
+ ezmsg/sigproc/messages.py,sha256=KpYCWWRD5itVAq0-Lf8qd7ErEE1A9qdm2p7O8Y4zEFA,955
15
+ ezmsg/sigproc/sampler.py,sha256=Zv5eq9CLpE8ui4HyZzBEqxwqBf--IbexS7zBx3qKp3w,12733
16
+ ezmsg/sigproc/scaler.py,sha256=2Hi4XWWvUtPOVOfIyX0Dr0cZEdbTobZOoMXKh9Vszlk,5944
17
+ ezmsg/sigproc/signalinjector.py,sha256=T1yvigk9WYujYqubd15UAF6hNmSqS9J7rCzaPYENoEA,2644
18
+ ezmsg/sigproc/slicer.py,sha256=ffawSqhO06lC5TXg_Ujg0eP2EuF98McQiUdlwxn3psI,5072
19
+ ezmsg/sigproc/spectral.py,sha256=_2qO6as4Nesmc9V1WW2EXNMH5pPz8aVTEcIPOi4-g2o,322
20
+ ezmsg/sigproc/spectrogram.py,sha256=16By4VWln7UvZ8IV_TdNAFaJDgkzvVKXWpnsoZoiW1M,3146
21
+ ezmsg/sigproc/spectrum.py,sha256=a75d6q6o3dAuqX6ZuFv6AfHYDOMuhA01OVCugzFMUSQ,9415
22
+ ezmsg/sigproc/synth.py,sha256=ygJRI9Seqhrg13lUhcA6XSpxcEr3LL1NupviSQLrC9o,18351
23
+ ezmsg/sigproc/wavelets.py,sha256=_3jI52NzksD1WNnHx03F2p4V2uGN_MtBbLsJGRjeyoE,6596
24
+ ezmsg/sigproc/window.py,sha256=A697XG2YyR9ywmzfxJIXovi6wPwDhBI9UUeShNJFKgM,13094
25
+ ezmsg/sigproc/math/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ ezmsg/sigproc/math/abs.py,sha256=hqS_sbLqAKSlguj3_rsfeWzOe6233tMxXfjDPg1fi2g,918
27
+ ezmsg/sigproc/math/clip.py,sha256=xIfc6fEfiNh7ILG3IYxcuCd5VBqmWXmDliGi_dKVJpM,1131
28
+ ezmsg/sigproc/math/difference.py,sha256=s3al1WzUZXsG2XYzOprRcKP-VD9J5FwYx578_SKG0vg,2183
29
+ ezmsg/sigproc/math/invert.py,sha256=H8df1u8OVb5TOkRC8p5EW7R17kHdLOYqXVH_axy5bkI,882
30
+ ezmsg/sigproc/math/log.py,sha256=1wuPvdjiI29tC_gRSntfpAebJ0rDhlapyLjG5QwXRpo,1496
31
+ ezmsg/sigproc/math/scale.py,sha256=iIQuJAr5pzCiMqzi2YdzLc1agQBKBOcngdNZlLO3X_o,1050
32
+ ezmsg_sigproc-1.4.1.dist-info/METADATA,sha256=ba6ETetMm_XUgNkyMcGTgeVItvVZWd9QEzPpYkh0e3s,2376
33
+ ezmsg_sigproc-1.4.1.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
34
+ ezmsg_sigproc-1.4.1.dist-info/licenses/LICENSE.txt,sha256=seu0tKhhAMPCUgc1XpXGGaCxY1YaYvFJwqFuQZAl2go,1100
35
+ ezmsg_sigproc-1.4.1.dist-info/RECORD,,
@@ -1,35 +0,0 @@
1
- ezmsg/sigproc/__init__.py,sha256=8K4IcOA3-pfzadoM6s2Sfg5460KlJUocGgyTJTJl96U,52
2
- ezmsg/sigproc/__version__.py,sha256=VriGPi1kVXIBM0YGAuhpE803XR-FNq1JvTW1Kz2us08,411
3
- ezmsg/sigproc/activation.py,sha256=-KgDSVmgYx8QhHM5UQHkMN6rHudeasuWOYhmM2_aWH0,2068
4
- ezmsg/sigproc/affinetransform.py,sha256=8glamFplQ0uwp-80U_StkF6a__tsh50JW7Tb77ZEQmw,8801
5
- ezmsg/sigproc/aggregate.py,sha256=8kVs2S2MUuTZkRwn-vh4utv8uoWwWrIkgalxcBdT39M,5490
6
- ezmsg/sigproc/bandpower.py,sha256=l_R_qcrRTLGYjg40VAinIHz24pdwET4AS4RXd2Ctcig,2126
7
- ezmsg/sigproc/base.py,sha256=VtlneWR0GAsWzAfqMzFy-WYPbeF8C3qVenqj7h_Cfek,1224
8
- ezmsg/sigproc/butterworthfilter.py,sha256=busvcDtVJx2ZHgenM4o0SDTeBbtXbPEXrClCRty0iF0,5233
9
- ezmsg/sigproc/decimate.py,sha256=zNxJXauE4Z54Yy7tpN9_oJuW0I0ppQRWhSpwb3Ri2gc,1473
10
- ezmsg/sigproc/downsample.py,sha256=Zuk_sCTk3eYOmyVsKGk9P6s-yUkfim_neBBc61ZVUKY,3304
11
- ezmsg/sigproc/ewmfilter.py,sha256=XzTmouRa7AnPjTiI32-oumwm4ZfOl6uPPUSldExNpAs,4516
12
- ezmsg/sigproc/filter.py,sha256=DHF-4CvxhNCGaAuVpn64wCxYuq5TpMH7bi0KKQNaOk0,8161
13
- ezmsg/sigproc/filterbank.py,sha256=K3Y2SJ7kfEp-CiNNSgzEM7sq1hbYaXOvufKSVyYnUEw,12252
14
- ezmsg/sigproc/messages.py,sha256=KpYCWWRD5itVAq0-Lf8qd7ErEE1A9qdm2p7O8Y4zEFA,955
15
- ezmsg/sigproc/sampler.py,sha256=3-O5nplutDcojb0CCbkTQsH8cZ8yKxqYts_02LryIKM,12762
16
- ezmsg/sigproc/scaler.py,sha256=eQuF3DL4NvxuEBjDgf2k4qqoctw5hIS_NIOfbOjOY0k,5823
17
- ezmsg/sigproc/signalinjector.py,sha256=HzQvI6jvkuX7miGHy6h0yXVRz_wvM3ecpQvquCV0H7g,2408
18
- ezmsg/sigproc/slicer.py,sha256=TcTI464UH9y1abCwT6eEMaqo7B1geuwJNNYScMUyyFM,4609
19
- ezmsg/sigproc/spectral.py,sha256=_2qO6as4Nesmc9V1WW2EXNMH5pPz8aVTEcIPOi4-g2o,322
20
- ezmsg/sigproc/spectrogram.py,sha256=osaqIhC04RbMnpHy5ecAxnYhQOn022oTvF2wbz-lKec,2977
21
- ezmsg/sigproc/spectrum.py,sha256=AmoPObVVvuXkUSClCExvKzLoasLyTlIwtRsrz1XFOLI,9366
22
- ezmsg/sigproc/synth.py,sha256=ygJRI9Seqhrg13lUhcA6XSpxcEr3LL1NupviSQLrC9o,18351
23
- ezmsg/sigproc/wavelets.py,sha256=sccj7xsIyHmsUL7XWYm7wxnNvIGEcpCZ8qsfRXSObiA,6488
24
- ezmsg/sigproc/window.py,sha256=bzOJDQRxDfcHNHrTgWkILGH9uYk4z0jy-JkCb8zguFQ,12599
25
- ezmsg/sigproc/math/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- ezmsg/sigproc/math/abs.py,sha256=8rtcdyMSGtnMQgGwbcW8Ky-3uRspkYD7lRdpcFl6uEE,608
27
- ezmsg/sigproc/math/clip.py,sha256=WBKe6wPTcqb041OwuUB5OGK-s_R3gc95qKBJY2Aspgo,779
28
- ezmsg/sigproc/math/difference.py,sha256=tacbt3KKQOhoYaFMlrASC2GhYQG1J6Hdaa9J1zg_Aw8,1735
29
- ezmsg/sigproc/math/invert.py,sha256=X6Pp0oYg5A3tpDxfI513xjYg2yXjsosR048AxCWzIwI,667
30
- ezmsg/sigproc/math/log.py,sha256=T3DST5gpva3lTHmbJkDWVpJyqEVeydUuZSOQCThbvIg,757
31
- ezmsg/sigproc/math/scale.py,sha256=Ge99e43m1tYoxwQEZ2y-jPq6XQn_IvuKLPaTT2y5vPI,746
32
- ezmsg_sigproc-1.3.3.dist-info/METADATA,sha256=05rs7K2AzE-kgy7eflcf_jywViILDF55qXn4JGaN2eg,2376
33
- ezmsg_sigproc-1.3.3.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
34
- ezmsg_sigproc-1.3.3.dist-info/licenses/LICENSE.txt,sha256=seu0tKhhAMPCUgc1XpXGGaCxY1YaYvFJwqFuQZAl2go,1100
35
- ezmsg_sigproc-1.3.3.dist-info/RECORD,,