ezmsg-baseproc 1.0.3__py3-none-any.whl → 1.2.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.
@@ -7,6 +7,22 @@ signal processing pipelines in ezmsg.
7
7
 
8
8
  from .__version__ import __version__ as __version__
9
9
 
10
+ # Clock and Counter
11
+ from .clock import (
12
+ Clock,
13
+ ClockProducer,
14
+ ClockSettings,
15
+ ClockState,
16
+ )
17
+
18
+ # Clock-driven producers
19
+ from .clockdriven import (
20
+ BaseClockDrivenProducer,
21
+ ClockDrivenSettings,
22
+ ClockDrivenSettingsType,
23
+ ClockDrivenState,
24
+ )
25
+
10
26
  # Composite processor classes
11
27
  from .composite import (
12
28
  CompositeProcessor,
@@ -14,6 +30,12 @@ from .composite import (
14
30
  CompositeStateful,
15
31
  _get_processor_message_type,
16
32
  )
33
+ from .counter import (
34
+ Counter,
35
+ CounterSettings,
36
+ CounterTransformer,
37
+ CounterTransformerState,
38
+ )
17
39
 
18
40
  # Base processor classes (non-stateful)
19
41
  from .processor import (
@@ -60,15 +82,18 @@ from .stateful import (
60
82
  from .units import (
61
83
  AdaptiveTransformerType,
62
84
  BaseAdaptiveTransformerUnit,
85
+ BaseClockDrivenProducerUnit,
63
86
  BaseConsumerUnit,
64
87
  BaseProcessorUnit,
65
88
  BaseProducerUnit,
66
89
  BaseTransformerUnit,
90
+ ClockDrivenProducerType,
67
91
  ConsumerType,
68
92
  GenAxisArray,
69
93
  ProducerType,
70
94
  TransformerType,
71
95
  get_base_adaptive_transformer_type,
96
+ get_base_clockdriven_producer_type,
72
97
  get_base_consumer_type,
73
98
  get_base_producer_type,
74
99
  get_base_transformer_type,
@@ -102,6 +127,7 @@ __all__ = [
102
127
  "ConsumerType",
103
128
  "TransformerType",
104
129
  "AdaptiveTransformerType",
130
+ "ClockDrivenProducerType",
105
131
  # Decorators
106
132
  "processor_state",
107
133
  # Base processor classes
@@ -117,6 +143,11 @@ __all__ = [
117
143
  "BaseStatefulTransformer",
118
144
  "BaseAdaptiveTransformer",
119
145
  "BaseAsyncTransformer",
146
+ # Clock-driven producers
147
+ "BaseClockDrivenProducer",
148
+ "ClockDrivenSettings",
149
+ "ClockDrivenSettingsType",
150
+ "ClockDrivenState",
120
151
  # Composite classes
121
152
  "CompositeStateful",
122
153
  "CompositeProcessor",
@@ -127,12 +158,14 @@ __all__ = [
127
158
  "BaseConsumerUnit",
128
159
  "BaseTransformerUnit",
129
160
  "BaseAdaptiveTransformerUnit",
161
+ "BaseClockDrivenProducerUnit",
130
162
  "GenAxisArray",
131
163
  # Type resolution helpers
132
164
  "get_base_producer_type",
133
165
  "get_base_consumer_type",
134
166
  "get_base_transformer_type",
135
167
  "get_base_adaptive_transformer_type",
168
+ "get_base_clockdriven_producer_type",
136
169
  "_get_base_processor_settings_type",
137
170
  "_get_base_processor_message_in_type",
138
171
  "_get_base_processor_message_out_type",
@@ -152,4 +185,13 @@ __all__ = [
152
185
  # Type utilities
153
186
  "check_message_type_compatibility",
154
187
  "resolve_typevar",
188
+ # Clock and Counter
189
+ "Clock",
190
+ "ClockProducer",
191
+ "ClockSettings",
192
+ "ClockState",
193
+ "Counter",
194
+ "CounterSettings",
195
+ "CounterTransformer",
196
+ "CounterTransformerState",
155
197
  ]
@@ -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 = '1.0.3'
32
- __version_tuple__ = version_tuple = (1, 0, 3)
31
+ __version__ = version = '1.2.0'
32
+ __version_tuple__ = version_tuple = (1, 2, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -0,0 +1,109 @@
1
+ """Clock generator for timing control."""
2
+
3
+ import asyncio
4
+ import math
5
+ import time
6
+ from dataclasses import field
7
+
8
+ import ezmsg.core as ez
9
+ from ezmsg.util.messages.axisarray import AxisArray
10
+
11
+ from .protocols import processor_state
12
+ from .stateful import BaseStatefulProducer
13
+ from .units import BaseProducerUnit
14
+
15
+
16
+ class ClockSettings(ez.Settings):
17
+ """Settings for :obj:`ClockProducer`."""
18
+
19
+ dispatch_rate: float = math.inf
20
+ """
21
+ Dispatch rate in Hz.
22
+ - Finite value (e.g., 100.0): Dispatch 100 times per second
23
+ - math.inf: Dispatch as fast as possible (no sleep)
24
+ """
25
+
26
+
27
+ @processor_state
28
+ class ClockState:
29
+ """State for :obj:`ClockProducer`."""
30
+
31
+ t_0: float = field(default_factory=time.monotonic)
32
+ """Start time (monotonic)."""
33
+
34
+ n_dispatch: int = 0
35
+ """Number of dispatches since reset."""
36
+
37
+
38
+ class ClockProducer(BaseStatefulProducer[ClockSettings, AxisArray.LinearAxis, ClockState]):
39
+ """
40
+ Produces clock ticks at a specified rate.
41
+
42
+ Each tick outputs a :obj:`AxisArray.LinearAxis` containing:
43
+ - ``gain``: 1/dispatch_rate (seconds per tick), or 0.0 if dispatch_rate is infinite
44
+ - ``offset``: Wall clock timestamp (time.monotonic)
45
+
46
+ This output type allows downstream components (like Counter) to know both
47
+ the timing of the tick and the nominal dispatch rate.
48
+ """
49
+
50
+ def _reset_state(self) -> None:
51
+ """Reset internal state."""
52
+ self._state.t_0 = time.monotonic()
53
+ self._state.n_dispatch = 0
54
+
55
+ def _make_output(self, timestamp: float) -> AxisArray.LinearAxis:
56
+ """Create LinearAxis output with gain and offset."""
57
+ if math.isinf(self.settings.dispatch_rate):
58
+ gain = 0.0
59
+ else:
60
+ gain = 1.0 / self.settings.dispatch_rate
61
+ return AxisArray.LinearAxis(gain=gain, offset=timestamp)
62
+
63
+ def __call__(self) -> AxisArray.LinearAxis:
64
+ """Synchronous clock production."""
65
+ if self._hash == -1:
66
+ self._reset_state()
67
+ self._hash = 0
68
+
69
+ now = time.monotonic()
70
+ if math.isfinite(self.settings.dispatch_rate):
71
+ target_time = self.state.t_0 + (self.state.n_dispatch + 1) / self.settings.dispatch_rate
72
+ if target_time > now:
73
+ time.sleep(target_time - now)
74
+ else:
75
+ target_time = now
76
+
77
+ self.state.n_dispatch += 1
78
+ return self._make_output(target_time)
79
+
80
+ async def _produce(self) -> AxisArray.LinearAxis:
81
+ """Generate next clock tick."""
82
+ now = time.monotonic()
83
+ if math.isfinite(self.settings.dispatch_rate):
84
+ target_time = self.state.t_0 + (self.state.n_dispatch + 1) / self.settings.dispatch_rate
85
+ if target_time > now:
86
+ await asyncio.sleep(target_time - now)
87
+ else:
88
+ target_time = now
89
+
90
+ self.state.n_dispatch += 1
91
+ return self._make_output(target_time)
92
+
93
+
94
+ class Clock(
95
+ BaseProducerUnit[
96
+ ClockSettings,
97
+ AxisArray.LinearAxis,
98
+ ClockProducer,
99
+ ]
100
+ ):
101
+ """
102
+ Clock unit that produces ticks at a specified rate.
103
+
104
+ Output is a :obj:`AxisArray.LinearAxis` with:
105
+ - ``gain``: 1/dispatch_rate (seconds per tick)
106
+ - ``offset``: Wall clock timestamp
107
+ """
108
+
109
+ SETTINGS = ClockSettings
@@ -0,0 +1,179 @@
1
+ """Clock-driven producer base classes for generating data synchronized to clock ticks."""
2
+
3
+ import typing
4
+ from abc import abstractmethod
5
+
6
+ import ezmsg.core as ez
7
+ from ezmsg.util.messages.axisarray import AxisArray, LinearAxis
8
+
9
+ from .protocols import StateType, processor_state
10
+ from .stateful import BaseStatefulProcessor
11
+
12
+
13
+ class ClockDrivenSettings(ez.Settings):
14
+ """
15
+ Base settings for clock-driven producers.
16
+
17
+ Subclass this to add your own settings while inheriting fs and n_time.
18
+
19
+ Example::
20
+
21
+ class SinGeneratorSettings(ClockDrivenSettings):
22
+ freq: float = 1.0
23
+ amp: float = 1.0
24
+ """
25
+
26
+ fs: float
27
+ """Output sampling rate in Hz."""
28
+
29
+ n_time: int | None = None
30
+ """
31
+ Samples per block.
32
+ - If specified: fixed chunk size (clock gain is ignored for determining chunk size)
33
+ - If None: derived from clock gain (fs * clock.gain), with fractional sample tracking
34
+ """
35
+
36
+
37
+ # Type variable for settings that extend ClockDrivenSettings
38
+ ClockDrivenSettingsType = typing.TypeVar("ClockDrivenSettingsType", bound=ClockDrivenSettings)
39
+
40
+
41
+ @processor_state
42
+ class ClockDrivenState:
43
+ """
44
+ Internal state for clock-driven producers.
45
+
46
+ Tracks sample counting and fractional sample accumulation.
47
+ Subclasses should extend this if they need additional state.
48
+ """
49
+
50
+ counter: int = 0
51
+ """Current sample counter (total samples produced)."""
52
+
53
+ fractional_samples: float = 0.0
54
+ """Accumulated fractional samples for variable chunk mode."""
55
+
56
+
57
+ class BaseClockDrivenProducer(
58
+ BaseStatefulProcessor[ClockDrivenSettingsType, AxisArray.LinearAxis, AxisArray, StateType],
59
+ typing.Generic[ClockDrivenSettingsType, StateType],
60
+ ):
61
+ """
62
+ Base class for clock-driven data producers.
63
+
64
+ Accepts clock ticks (LinearAxis) as input and produces AxisArray output.
65
+ Handles all the timing/counter logic internally, so subclasses only need
66
+ to implement the data generation logic.
67
+
68
+ This eliminates the need for the Clock → Counter → Generator pattern
69
+ by combining the Counter functionality into the generator base class.
70
+
71
+ Subclasses must implement:
72
+ - ``_reset_state(time_axis)``: Initialize any state needed for production
73
+ - ``_produce(n_samples, time_axis)``: Generate the actual output data
74
+
75
+ Example::
76
+
77
+ @processor_state
78
+ class SinState(ClockDrivenState):
79
+ ang_freq: float = 0.0
80
+
81
+ class SinProducer(BaseClockDrivenProducer[SinSettings, SinState]):
82
+ def _reset_state(self, time_axis: AxisArray.TimeAxis) -> None:
83
+ self._state.ang_freq = 2 * np.pi * self.settings.fs
84
+
85
+ def _produce(self, n_samples: int, time_axis: AxisArray.TimeAxis) -> AxisArray:
86
+ t = (np.arange(n_samples) + self._state.counter) * time_axis.gain
87
+ data = np.sin(self._state.ang_freq * t)
88
+ return AxisArray(data=data, dims=["time"], axes={"time": time_axis})
89
+ """
90
+
91
+ def _hash_message(self, message: AxisArray.LinearAxis) -> int:
92
+ # Return constant hash - state should not reset based on clock rate changes.
93
+ # The producer maintains continuity regardless of clock rate changes.
94
+ return 0
95
+
96
+ def _compute_samples_and_offset(self, clock_tick: AxisArray.LinearAxis) -> tuple[int, float] | None:
97
+ """
98
+ Compute number of samples and time offset from a clock tick.
99
+
100
+ Returns:
101
+ Tuple of (n_samples, offset) or None if no samples to produce yet.
102
+
103
+ Raises:
104
+ ValueError: If clock gain is 0 (AFAP mode) and n_time is not specified.
105
+ """
106
+ if self.settings.n_time is not None:
107
+ # Fixed chunk size mode
108
+ n_samples = self.settings.n_time
109
+ if clock_tick.gain == 0.0:
110
+ # AFAP mode - synthetic offset based on counter
111
+ offset = self._state.counter / self.settings.fs
112
+ else:
113
+ # Use clock's timestamp
114
+ offset = clock_tick.offset
115
+ else:
116
+ # Variable chunk size mode - derive from clock gain
117
+ if clock_tick.gain == 0.0:
118
+ raise ValueError("Cannot use clock with gain=0 (AFAP) without specifying n_time")
119
+
120
+ # Calculate samples including fractional accumulation
121
+ samples_float = self.settings.fs * clock_tick.gain + self._state.fractional_samples
122
+ n_samples = int(samples_float + 1e-9)
123
+ self._state.fractional_samples = samples_float - n_samples
124
+
125
+ if n_samples == 0:
126
+ return None
127
+
128
+ offset = clock_tick.offset
129
+
130
+ return n_samples, offset
131
+
132
+ @abstractmethod
133
+ def _reset_state(self, time_axis: LinearAxis) -> None:
134
+ """
135
+ Reset/initialize state for production.
136
+
137
+ Called once before the first call to _produce, or when state needs resetting.
138
+ Use this to pre-compute values, create templates, etc.
139
+
140
+ Args:
141
+ time_axis: TimeAxis with the output sampling rate (fs) and initial offset.
142
+ """
143
+ ...
144
+
145
+ @abstractmethod
146
+ def _produce(self, n_samples: int, time_axis: LinearAxis) -> AxisArray:
147
+ """
148
+ Generate output data for this chunk.
149
+
150
+ Args:
151
+ n_samples: Number of samples to generate.
152
+ time_axis: TimeAxis with correct offset and gain (1/fs) for this chunk.
153
+
154
+ Returns:
155
+ AxisArray containing the generated data. The time axis should use
156
+ the provided time_axis or one derived from it.
157
+ """
158
+ ...
159
+
160
+ def _process(self, clock_tick: LinearAxis) -> AxisArray | None:
161
+ """
162
+ Process a clock tick and produce output.
163
+
164
+ Handles all the counter/timing logic internally, then calls _produce.
165
+ """
166
+ result = self._compute_samples_and_offset(clock_tick)
167
+ if result is None:
168
+ return None
169
+
170
+ n_samples, offset = result
171
+ time_axis = AxisArray.TimeAxis(fs=self.settings.fs, offset=offset)
172
+
173
+ # Call subclass production method
174
+ output = self._produce(n_samples, time_axis)
175
+
176
+ # Update counter
177
+ self._state.counter += n_samples
178
+
179
+ return output
@@ -0,0 +1,67 @@
1
+ """Counter generator for sample counting and timing."""
2
+
3
+ import numpy as np
4
+ from ezmsg.util.messages.axisarray import AxisArray, LinearAxis, replace
5
+
6
+ from .clockdriven import (
7
+ BaseClockDrivenProducer,
8
+ ClockDrivenSettings,
9
+ ClockDrivenState,
10
+ )
11
+ from .protocols import processor_state
12
+ from .units import BaseClockDrivenProducerUnit
13
+
14
+
15
+ class CounterSettings(ClockDrivenSettings):
16
+ """Settings for :obj:`Counter` and :obj:`CounterTransformer`."""
17
+
18
+ mod: int | None = None
19
+ """If set, counter values rollover at this modulus."""
20
+
21
+
22
+ @processor_state
23
+ class CounterTransformerState(ClockDrivenState):
24
+ """State for :obj:`CounterTransformer`."""
25
+
26
+ template: AxisArray | None = None
27
+
28
+
29
+ class CounterTransformer(BaseClockDrivenProducer[CounterSettings, CounterTransformerState]):
30
+ """
31
+ Transforms clock ticks (LinearAxis) into AxisArray counter values.
32
+
33
+ Each clock tick produces a block of counter values. The block size is either
34
+ fixed (n_time setting) or derived from the clock's gain (fs * gain).
35
+ """
36
+
37
+ def _reset_state(self, time_axis: LinearAxis) -> None:
38
+ """Reset state - initialize template for counter output."""
39
+ self._state.template = AxisArray(
40
+ data=np.array([], dtype=int),
41
+ dims=["time"],
42
+ axes={"time": time_axis},
43
+ key="counter",
44
+ )
45
+
46
+ def _produce(self, n_samples: int, time_axis: LinearAxis) -> AxisArray:
47
+ """Generate counter values for this chunk."""
48
+ # Generate counter data (using pre-increment counter value)
49
+ block_samp = np.arange(self._state.counter, self._state.counter + n_samples)
50
+ if self.settings.mod is not None:
51
+ block_samp = block_samp % self.settings.mod
52
+
53
+ return replace(
54
+ self._state.template,
55
+ data=block_samp,
56
+ axes={"time": time_axis},
57
+ )
58
+
59
+
60
+ class Counter(BaseClockDrivenProducerUnit[CounterSettings, CounterTransformer]):
61
+ """
62
+ Transforms clock ticks into monotonically increasing counter values as AxisArray.
63
+
64
+ Receives timing from INPUT_CLOCK (LinearAxis from Clock) and outputs AxisArray.
65
+ """
66
+
67
+ SETTINGS = CounterSettings
ezmsg/baseproc/units.py CHANGED
@@ -7,8 +7,9 @@ from abc import ABC, abstractmethod
7
7
 
8
8
  import ezmsg.core as ez
9
9
  from ezmsg.util.generator import GenState
10
- from ezmsg.util.messages.axisarray import AxisArray
10
+ from ezmsg.util.messages.axisarray import AxisArray, LinearAxis
11
11
 
12
+ from .clockdriven import BaseClockDrivenProducer
12
13
  from .composite import CompositeProcessor
13
14
  from .processor import BaseConsumer, BaseProducer, BaseTransformer
14
15
  from .protocols import MessageInType, MessageOutType, SettingsType
@@ -25,6 +26,7 @@ TransformerType = typing.TypeVar(
25
26
  bound=BaseTransformer | BaseStatefulTransformer | CompositeProcessor,
26
27
  )
27
28
  AdaptiveTransformerType = typing.TypeVar("AdaptiveTransformerType", bound=BaseAdaptiveTransformer)
29
+ ClockDrivenProducerType = typing.TypeVar("ClockDrivenProducerType", bound=BaseClockDrivenProducer)
28
30
 
29
31
 
30
32
  def get_base_producer_type(cls: type) -> type:
@@ -43,6 +45,10 @@ def get_base_adaptive_transformer_type(cls: type) -> type:
43
45
  return resolve_typevar(cls, AdaptiveTransformerType)
44
46
 
45
47
 
48
+ def get_base_clockdriven_producer_type(cls: type) -> type:
49
+ return resolve_typevar(cls, ClockDrivenProducerType)
50
+
51
+
46
52
  # --- Base classes for ezmsg Unit with specific processing capabilities ---
47
53
  class BaseProducerUnit(ez.Unit, ABC, typing.Generic[SettingsType, MessageOutType, ProducerType]):
48
54
  """
@@ -240,6 +246,47 @@ class BaseAdaptiveTransformerUnit(
240
246
  await self.processor.apartial_fit(msg)
241
247
 
242
248
 
249
+ class BaseClockDrivenProducerUnit(
250
+ BaseProcessorUnit[SettingsType],
251
+ ABC,
252
+ typing.Generic[SettingsType, ClockDrivenProducerType],
253
+ ):
254
+ """
255
+ Base class for clock-driven producer units.
256
+
257
+ These units receive clock ticks (LinearAxis) and produce AxisArray output.
258
+ This simplifies the Clock → Counter → Generator pattern by combining
259
+ the counter functionality into the generator.
260
+
261
+ Implement a new Unit as follows::
262
+
263
+ class SinGeneratorUnit(BaseClockDrivenProducerUnit[
264
+ SinGeneratorSettings, # SettingsType (must extend ClockDrivenSettings)
265
+ SinProducer, # ClockDrivenProducerType
266
+ ]):
267
+ SETTINGS = SinGeneratorSettings
268
+
269
+ Where SinGeneratorSettings extends ClockDrivenSettings and SinProducer
270
+ extends BaseClockDrivenProducer.
271
+ """
272
+
273
+ INPUT_CLOCK = ez.InputStream(LinearAxis)
274
+ OUTPUT_SIGNAL = ez.OutputStream(AxisArray)
275
+
276
+ def create_processor(self) -> None:
277
+ """Create the clock-driven producer instance from settings."""
278
+ producer_type = get_base_clockdriven_producer_type(self.__class__)
279
+ self.processor = producer_type(settings=self.SETTINGS)
280
+
281
+ @ez.subscriber(INPUT_CLOCK, zero_copy=True)
282
+ @ez.publisher(OUTPUT_SIGNAL)
283
+ @profile_subpub(trace_oldest=False)
284
+ async def on_clock(self, clock_tick: LinearAxis) -> typing.AsyncGenerator:
285
+ result = await self.processor.__acall__(clock_tick)
286
+ if result is not None:
287
+ yield self.OUTPUT_SIGNAL, result
288
+
289
+
243
290
  # Legacy class
244
291
  class GenAxisArray(ez.Unit):
245
292
  STATE = GenState
@@ -11,6 +11,10 @@ def resolve_typevar(cls: type, target_typevar: typing.TypeVar) -> type:
11
11
  and checks the original bases of each class in the MRO for the TypeVar.
12
12
  If the TypeVar is found, it returns the concrete type bound to it.
13
13
  If the TypeVar is not found, it raises a TypeError.
14
+
15
+ If the resolved type is itself a TypeVar, this function recursively
16
+ resolves it until a concrete type is found.
17
+
14
18
  Args:
15
19
  cls (type): The class to inspect.
16
20
  target_typevar (typing.TypeVar): The TypeVar to resolve.
@@ -30,7 +34,11 @@ def resolve_typevar(cls: type, target_typevar: typing.TypeVar) -> type:
30
34
  index = params.index(target_typevar)
31
35
  args = typing.get_args(orig_base)
32
36
  try:
33
- return args[index]
37
+ resolved = args[index]
38
+ # If the resolved type is itself a TypeVar, resolve it recursively
39
+ if isinstance(resolved, typing.TypeVar):
40
+ return resolve_typevar(cls, resolved)
41
+ return resolved
34
42
  except IndexError:
35
43
  pass
36
44
  raise TypeError(f"Could not resolve {target_typevar} in {cls}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ezmsg-baseproc
3
- Version: 1.0.3
3
+ Version: 1.2.0
4
4
  Summary: Base processor classes and protocols for ezmsg signal processing pipelines
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
@@ -12,7 +12,7 @@ Description-Content-Type: text/markdown
12
12
 
13
13
  # ezmsg-baseproc
14
14
 
15
- Base processor classes and protocols for building signal processing pipelines in [ezmsg](https://github.com/ezmsg-org/ezmsg).
15
+ Base processor classes and protocols for building message-processing components in [ezmsg](https://github.com/ezmsg-org/ezmsg).
16
16
 
17
17
  ## Installation
18
18
 
@@ -20,30 +20,26 @@ Base processor classes and protocols for building signal processing pipelines in
20
20
  pip install ezmsg-baseproc
21
21
  ```
22
22
 
23
- ## Overview
23
+ Or install the latest development version:
24
24
 
25
- This package provides the foundational processor architecture for ezmsg signal processing:
25
+ ```bash
26
+ pip install git+https://github.com/ezmsg-org/ezmsg-baseproc@dev
27
+ ```
26
28
 
27
- - **Protocols** - Type definitions for processors, transformers, consumers, and producers
28
- - **Base Classes** - Abstract base classes for building stateless and stateful processors
29
- - **Composite Processors** - Classes for chaining processors into pipelines
30
- - **Unit Wrappers** - ezmsg Unit base classes that wrap processors for graph integration
29
+ ## Overview
31
30
 
32
- ## Module Structure
31
+ ``ezmsg-baseproc`` provides abstract base classes for creating message processors that can be used both standalone and within ezmsg pipelines. The package offers a consistent pattern for building:
33
32
 
34
- ```
35
- ezmsg.baseproc/
36
- ├── protocols.py # Protocol definitions and type variables
37
- ├── processor.py # Base non-stateful processors
38
- ├── stateful.py # Stateful processor base classes
39
- ├── composite.py # CompositeProcessor and CompositeProducer
40
- ├── units.py # ezmsg Unit wrappers
41
- └── util/
42
- ├── asio.py # Async/sync utilities
43
- ├── message.py # SampleMessage definitions
44
- ├── profile.py # Profiling decorators
45
- └── typeresolution.py # Type resolution helpers
46
- ```
33
+ * **Protocols** - Type definitions for processors, transformers, consumers, and producers
34
+ * **Processors** - Transform input messages to output messages
35
+ * **Producers** - Generate output messages without requiring input
36
+ * **Consumers** - Accept input messages without producing output
37
+ * **Transformers** - A specific type of processor with typed input/output
38
+ * **Stateful variants** - Processors that maintain state across invocations
39
+ * **Adaptive transformers** - Transformers that can be trained via ``partial_fit``
40
+ * **Composite processors** - Chain multiple processors together efficiently
41
+
42
+ All base classes support both synchronous and asynchronous operation, making them suitable for offline analysis and real-time streaming applications.
47
43
 
48
44
  ## Usage
49
45
 
@@ -100,6 +96,7 @@ We use [`uv`](https://docs.astral.sh/uv/getting-started/installation/) for devel
100
96
  2. Clone and cd into the repository
101
97
  3. Run `uv sync` to create a `.venv` and install dependencies
102
98
  4. Run `uv run pytest tests` to run tests
99
+ 5. (Optional) Install pre-commit hooks: `uv run pre-commit install`
103
100
 
104
101
  ## License
105
102
 
@@ -0,0 +1,19 @@
1
+ ezmsg/baseproc/__init__.py,sha256=cSkyXkfPvIiAxe6maHiZvJpFbdRw3xTEitBClXeehec,4755
2
+ ezmsg/baseproc/__version__.py,sha256=-uLONazCO1SzFfcY-K6A1keL--LIVfTYccGX6ciADac,704
3
+ ezmsg/baseproc/clock.py,sha256=jRJGxaWWBek503OcGRYsW4Qc7Lt4m-tVl1wn_v-qCIk,3254
4
+ ezmsg/baseproc/clockdriven.py,sha256=ckPZSVHZfYfjFRHDCERUWjDwyQgOu-aRTNsJ3EDFBaI,6161
5
+ ezmsg/baseproc/composite.py,sha256=Lin4K_rmS2Tnxt-m8daP-PUyeeqL4id5JkVh-AUNrQw,14901
6
+ ezmsg/baseproc/counter.py,sha256=kcBPiVxMPULp4ojnVESNw7mn_4v0xSODfASHrL83GtM,2168
7
+ ezmsg/baseproc/processor.py,sha256=Ir9FtNuVG4yc-frwNxoYrlld99ff1mXwwGWaHxEJ6tY,8056
8
+ ezmsg/baseproc/protocols.py,sha256=O3Qp0ymE9Ovlmh8t22v-lMmFzuWK0D93REAYMnJV3xA,5106
9
+ ezmsg/baseproc/stateful.py,sha256=-jjAZIyJA5eiTECi1fSfazfqgv__RtyqPp1ZvLFFIDI,11424
10
+ ezmsg/baseproc/units.py,sha256=byFijVLEZFO145HE74sZk1_qpCu6nFjB8-vSYz9Grds,12077
11
+ ezmsg/baseproc/util/__init__.py,sha256=hvMUJOBuqioER50GZ5-GZiQbQ9NtQYEze13ZlR2jbMA,37
12
+ ezmsg/baseproc/util/asio.py,sha256=0sF5oDc58DSLlcEgoUpNiqjjcbqnZhjSpQrXn6IdosM,4960
13
+ ezmsg/baseproc/util/message.py,sha256=l_b1b6bXX8N6VF9RbUELzsHs73cKkDURBdIr0lt3CY0,909
14
+ ezmsg/baseproc/util/profile.py,sha256=MOQDsFsW6ddXT0uAOgytW3aK_AZW5ieA16Pz2hWuE2o,6189
15
+ ezmsg/baseproc/util/typeresolution.py,sha256=5on4QcrYd1rxsRoDEqivNjuWT5BkU-Wg7XdTNaOircI,3485
16
+ ezmsg_baseproc-1.2.0.dist-info/METADATA,sha256=H2jTn5VSw0pEwQWLdd3Hu0G1OCbbKM-On4AmKyyyfm4,3415
17
+ ezmsg_baseproc-1.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
18
+ ezmsg_baseproc-1.2.0.dist-info/licenses/LICENSE,sha256=BDD8rfac1Ur7mp0_3izEdr6fHgSA3Or6U1Kb0ZAWsow,1066
19
+ ezmsg_baseproc-1.2.0.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- ezmsg/baseproc/__init__.py,sha256=zmhdrRTnj3-ilBitu6zBniV04HJXc_fM_tKYiapFGko,3830
2
- ezmsg/baseproc/__version__.py,sha256=l8k828IdTfzXAlmx4oT8GsiIf2eeMAlFDALjoYk-jrU,704
3
- ezmsg/baseproc/composite.py,sha256=Lin4K_rmS2Tnxt-m8daP-PUyeeqL4id5JkVh-AUNrQw,14901
4
- ezmsg/baseproc/processor.py,sha256=Ir9FtNuVG4yc-frwNxoYrlld99ff1mXwwGWaHxEJ6tY,8056
5
- ezmsg/baseproc/protocols.py,sha256=O3Qp0ymE9Ovlmh8t22v-lMmFzuWK0D93REAYMnJV3xA,5106
6
- ezmsg/baseproc/stateful.py,sha256=-jjAZIyJA5eiTECi1fSfazfqgv__RtyqPp1ZvLFFIDI,11424
7
- ezmsg/baseproc/units.py,sha256=TRhjDKw0lqUUst7BHYJKP3AhGpRd6mvcdKxULfeWjA0,10283
8
- ezmsg/baseproc/util/__init__.py,sha256=hvMUJOBuqioER50GZ5-GZiQbQ9NtQYEze13ZlR2jbMA,37
9
- ezmsg/baseproc/util/asio.py,sha256=0sF5oDc58DSLlcEgoUpNiqjjcbqnZhjSpQrXn6IdosM,4960
10
- ezmsg/baseproc/util/message.py,sha256=l_b1b6bXX8N6VF9RbUELzsHs73cKkDURBdIr0lt3CY0,909
11
- ezmsg/baseproc/util/profile.py,sha256=MOQDsFsW6ddXT0uAOgytW3aK_AZW5ieA16Pz2hWuE2o,6189
12
- ezmsg/baseproc/util/typeresolution.py,sha256=WCHHYIrMMZ1CfwJWVlJPQgFyY2gnGRNFJVQynAsee7Y,3113
13
- ezmsg_baseproc-1.0.3.dist-info/METADATA,sha256=nOK4zkV08Nx1azdXd7js_OT0kQqomMq0Z2s8AoPSPd4,3320
14
- ezmsg_baseproc-1.0.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
15
- ezmsg_baseproc-1.0.3.dist-info/licenses/LICENSE,sha256=BDD8rfac1Ur7mp0_3izEdr6fHgSA3Or6U1Kb0ZAWsow,1066
16
- ezmsg_baseproc-1.0.3.dist-info/RECORD,,