ezmsg-baseproc 1.0.3__tar.gz → 1.2.0__tar.gz
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_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/PKG-INFO +19 -22
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/README.md +18 -21
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/docs/source/guides/ProcessorsBase.md +25 -20
- ezmsg_baseproc-1.2.0/docs/source/guides/how-tos/processors/clockdriven.rst +224 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/docs/source/guides/how-tos/processors/content-processors.rst +1 -0
- ezmsg_baseproc-1.2.0/docs/source/index.md +18 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/src/ezmsg/baseproc/__init__.py +42 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/src/ezmsg/baseproc/__version__.py +2 -2
- ezmsg_baseproc-1.2.0/src/ezmsg/baseproc/clock.py +109 -0
- ezmsg_baseproc-1.2.0/src/ezmsg/baseproc/clockdriven.py +179 -0
- ezmsg_baseproc-1.2.0/src/ezmsg/baseproc/counter.py +67 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/src/ezmsg/baseproc/units.py +48 -1
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/src/ezmsg/baseproc/util/typeresolution.py +9 -1
- ezmsg_baseproc-1.2.0/tests/test_clock.py +83 -0
- ezmsg_baseproc-1.2.0/tests/test_clockdriven.py +373 -0
- ezmsg_baseproc-1.2.0/tests/test_counter.py +180 -0
- ezmsg_baseproc-1.0.3/docs/source/index.rst +0 -87
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/.github/workflows/docs.yml +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/.github/workflows/python-publish.yml +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/.github/workflows/python-tests.yml +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/.gitignore +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/.pre-commit-config.yaml +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/LICENSE +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/docs/Makefile +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/docs/make.bat +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/docs/source/_templates/autosummary/module.rst +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/docs/source/api/index.rst +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/docs/source/conf.py +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/docs/source/guides/how-tos/processors/adaptive.rst +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/docs/source/guides/how-tos/processors/checkpoint.rst +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/docs/source/guides/how-tos/processors/composite.rst +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/docs/source/guides/how-tos/processors/processor.rst +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/docs/source/guides/how-tos/processors/standalone.rst +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/docs/source/guides/how-tos/processors/stateful.rst +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/docs/source/guides/how-tos/processors/unit.rst +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/pyproject.toml +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/src/ezmsg/baseproc/composite.py +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/src/ezmsg/baseproc/processor.py +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/src/ezmsg/baseproc/protocols.py +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/src/ezmsg/baseproc/stateful.py +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/src/ezmsg/baseproc/util/__init__.py +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/src/ezmsg/baseproc/util/asio.py +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/src/ezmsg/baseproc/util/message.py +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/src/ezmsg/baseproc/util/profile.py +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/tests/test_baseproc.py +0 -0
- {ezmsg_baseproc-1.0.3 → ezmsg_baseproc-1.2.0}/tests/test_profile.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ezmsg-baseproc
|
|
3
|
-
Version: 1.0
|
|
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
|
|
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
|
-
|
|
23
|
+
Or install the latest development version:
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
```bash
|
|
26
|
+
pip install git+https://github.com/ezmsg-org/ezmsg-baseproc@dev
|
|
27
|
+
```
|
|
26
28
|
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ezmsg-baseproc
|
|
2
2
|
|
|
3
|
-
Base processor classes and protocols for building
|
|
3
|
+
Base processor classes and protocols for building message-processing components in [ezmsg](https://github.com/ezmsg-org/ezmsg).
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,30 +8,26 @@ Base processor classes and protocols for building signal processing pipelines in
|
|
|
8
8
|
pip install ezmsg-baseproc
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Or install the latest development version:
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
```bash
|
|
14
|
+
pip install git+https://github.com/ezmsg-org/ezmsg-baseproc@dev
|
|
15
|
+
```
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
- **Base Classes** - Abstract base classes for building stateless and stateful processors
|
|
17
|
-
- **Composite Processors** - Classes for chaining processors into pipelines
|
|
18
|
-
- **Unit Wrappers** - ezmsg Unit base classes that wrap processors for graph integration
|
|
17
|
+
## Overview
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
``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:
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
├── profile.py # Profiling decorators
|
|
33
|
-
└── typeresolution.py # Type resolution helpers
|
|
34
|
-
```
|
|
21
|
+
* **Protocols** - Type definitions for processors, transformers, consumers, and producers
|
|
22
|
+
* **Processors** - Transform input messages to output messages
|
|
23
|
+
* **Producers** - Generate output messages without requiring input
|
|
24
|
+
* **Consumers** - Accept input messages without producing output
|
|
25
|
+
* **Transformers** - A specific type of processor with typed input/output
|
|
26
|
+
* **Stateful variants** - Processors that maintain state across invocations
|
|
27
|
+
* **Adaptive transformers** - Transformers that can be trained via ``partial_fit``
|
|
28
|
+
* **Composite processors** - Chain multiple processors together efficiently
|
|
29
|
+
|
|
30
|
+
All base classes support both synchronous and asynchronous operation, making them suitable for offline analysis and real-time streaming applications.
|
|
35
31
|
|
|
36
32
|
## Usage
|
|
37
33
|
|
|
@@ -88,6 +84,7 @@ We use [`uv`](https://docs.astral.sh/uv/getting-started/installation/) for devel
|
|
|
88
84
|
2. Clone and cd into the repository
|
|
89
85
|
3. Run `uv sync` to create a `.venv` and install dependencies
|
|
90
86
|
4. Run `uv run pytest tests` to run tests
|
|
87
|
+
5. (Optional) Install pre-commit hooks: `uv run pre-commit install`
|
|
91
88
|
|
|
92
89
|
## License
|
|
93
90
|
|
|
@@ -6,12 +6,13 @@ The `ezmsg.baseproc` module contains the base classes for message processors. Th
|
|
|
6
6
|
|
|
7
7
|
### Generic TypeVars
|
|
8
8
|
|
|
9
|
-
| Idx | Class
|
|
10
|
-
|
|
11
|
-
| 1 | `MessageInType` (Mi)
|
|
12
|
-
| 2 | `MessageOutType` (Mo)
|
|
13
|
-
| 3 | `SettingsType`
|
|
14
|
-
| 4 | `StateType` (St)
|
|
9
|
+
| Idx | Class | Description |
|
|
10
|
+
|-----|----------------------------|----------------------------------------------------------------------------|
|
|
11
|
+
| 1 | `MessageInType` (Mi) | for messages passed to a consumer, processor, or transformer |
|
|
12
|
+
| 2 | `MessageOutType` (Mo) | for messages returned by a producer, processor, or transformer |
|
|
13
|
+
| 3 | `SettingsType` | bound to ez.Settings |
|
|
14
|
+
| 4 | `StateType` (St) | bound to ProcessorState which is simply ez.State with a `hash: int` field. |
|
|
15
|
+
| 5 | `ClockDrivenSettingsType` | bound to `ClockDrivenSettings` (provides `fs` and `n_time`) |
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
### Protocols
|
|
@@ -47,6 +48,7 @@ Note: `__call__` and `partial_fit` both have asynchronous alternatives: `__acall
|
|
|
47
48
|
| 10 | `BaseAsyncTransformer` | 8 | 8 | `__acall__` wraps abstract `_aprocess`; `__call__` runs `__acall__`. |
|
|
48
49
|
| 11 | `CompositeProcessor` | 1 | 5 | Methods iterate over sequence of processors created in `_initialize_processors`. |
|
|
49
50
|
| 12 | `CompositeProducer` | 2 | 6 | Similar to `CompositeProcessor`, but first processor must be a producer. |
|
|
51
|
+
| 13 | `BaseClockDrivenProducer` | 5 | 8 | Clock-driven data generator. Implements `_produce(n_samples, time_axis)`. |
|
|
50
52
|
|
|
51
53
|
NOTES:
|
|
52
54
|
1. Producers do not inherit from `BaseProcessor`, so concrete implementations should subclass `BaseProducer` or `BaseStatefulProducer`.
|
|
@@ -60,25 +62,27 @@ do not inherit from `BaseStatefulProcessor` and `BaseStatefulProducer`. They acc
|
|
|
60
62
|
|
|
61
63
|
### Generic TypeVars for ezmsg Units
|
|
62
64
|
|
|
63
|
-
| Idx | Class
|
|
64
|
-
|
|
65
|
-
| 5 | `ProducerType`
|
|
66
|
-
| 6 | `ConsumerType`
|
|
67
|
-
| 7 | `TransformerType`
|
|
68
|
-
| 8 | `AdaptiveTransformerType`
|
|
65
|
+
| Idx | Class | Description |
|
|
66
|
+
|-----|----------------------------|------------------------------------------------------------------------------------------------------------------|
|
|
67
|
+
| 5 | `ProducerType` | bound to `BaseProducer` (hence, also `BaseStatefulProducer`, `CompositeProducer`) |
|
|
68
|
+
| 6 | `ConsumerType` | bound to `BaseConsumer`, `BaseStatefulConsumer` |
|
|
69
|
+
| 7 | `TransformerType` | bound to `BaseTransformer`, `BaseStatefulTransformer`, `CompositeProcessor` (hence, also `BaseAsyncTransformer`) |
|
|
70
|
+
| 8 | `AdaptiveTransformerType` | bound to `BaseAdaptiveTransformer` |
|
|
71
|
+
| 9 | `ClockDrivenProducerType` | bound to `BaseClockDrivenProducer` |
|
|
69
72
|
|
|
70
73
|
|
|
71
74
|
### Abstract implementations (Base Classes) for ezmsg Units using processors:
|
|
72
75
|
|
|
73
|
-
| Idx | Class
|
|
74
|
-
|
|
75
|
-
| 1 | `BaseProcessorUnit`
|
|
76
|
-
| 2 | `BaseProducerUnit`
|
|
77
|
-
| 3 | `BaseConsumerUnit`
|
|
78
|
-
| 4 | `BaseTransformerUnit`
|
|
79
|
-
| 5 | `BaseAdaptiveTransformerUnit`
|
|
76
|
+
| Idx | Class | Parents | Expected TypeVars |
|
|
77
|
+
|-----|--------------------------------|---------|----------------------------|
|
|
78
|
+
| 1 | `BaseProcessorUnit` | - | - |
|
|
79
|
+
| 2 | `BaseProducerUnit` | - | `ProducerType` |
|
|
80
|
+
| 3 | `BaseConsumerUnit` | 1 | `ConsumerType` |
|
|
81
|
+
| 4 | `BaseTransformerUnit` | 1 | `TransformerType` |
|
|
82
|
+
| 5 | `BaseAdaptiveTransformerUnit` | 1 | `AdaptiveTransformerType` |
|
|
83
|
+
| 6 | `BaseClockDrivenProducerUnit` | 1 | `ClockDrivenProducerType` |
|
|
80
84
|
|
|
81
|
-
Note, it is strongly recommended to use `BaseConsumerUnit`, `BaseTransformerUnit`, or `
|
|
85
|
+
Note, it is strongly recommended to use `BaseConsumerUnit`, `BaseTransformerUnit`, `BaseAdaptiveTransformerUnit`, or `BaseClockDrivenProducerUnit` for implementing concrete subclasses rather than `BaseProcessorUnit`.
|
|
82
86
|
|
|
83
87
|
|
|
84
88
|
## Implementing a custom standalone processor
|
|
@@ -125,6 +129,7 @@ flowchart TD
|
|
|
125
129
|
* For stateful processors that need to respond to a change in the incoming data, implement `_hash_message`.
|
|
126
130
|
* For adaptive transformers, implement `partial_fit`.
|
|
127
131
|
* For chains of processors (`CompositeProcessor`/ `CompositeProducer`), need to implement `_initialize_processors`.
|
|
132
|
+
* For clock-driven producers (`BaseClockDrivenProducer`), implement `_reset_state(time_axis)` and `_produce(n_samples, time_axis)`. See the [clock-driven how-to guide](how-tos/processors/clockdriven.rst).
|
|
128
133
|
* See processors in `ezmsg.sigproc` for signal processing examples, or `ezmsg.learn` for machine learning examples.
|
|
129
134
|
5. Override non-abstract methods if you need special behaviour.
|
|
130
135
|
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
How to implement a clock-driven producer?
|
|
2
|
+
#########################################
|
|
3
|
+
|
|
4
|
+
Clock-driven producers generate data synchronized to clock ticks. They are useful
|
|
5
|
+
for signal generators, simulators, and other components that need to produce
|
|
6
|
+
timed data streams.
|
|
7
|
+
|
|
8
|
+
The ``BaseClockDrivenProducer`` base class simplifies this pattern by handling
|
|
9
|
+
all the timing and sample counting logic internally. You only need to implement
|
|
10
|
+
the data generation.
|
|
11
|
+
|
|
12
|
+
When to use BaseClockDrivenProducer
|
|
13
|
+
===================================
|
|
14
|
+
|
|
15
|
+
Use ``BaseClockDrivenProducer`` when you need to:
|
|
16
|
+
|
|
17
|
+
- Generate synthetic signals (sine waves, noise, test patterns)
|
|
18
|
+
- Simulate sensor data at a specific sample rate
|
|
19
|
+
- Produce timed data streams driven by a ``Clock``
|
|
20
|
+
|
|
21
|
+
This base class eliminates the need for the ``Clock → Counter → Generator``
|
|
22
|
+
pattern by combining the counter functionality into the generator.
|
|
23
|
+
|
|
24
|
+
Basic Structure
|
|
25
|
+
===============
|
|
26
|
+
|
|
27
|
+
A clock-driven producer consists of three parts:
|
|
28
|
+
|
|
29
|
+
1. **Settings** - Extends ``ClockDrivenSettings`` (which provides ``fs`` and ``n_time``)
|
|
30
|
+
2. **State** - Extends ``ClockDrivenState`` (which provides ``counter`` and ``fractional_samples``)
|
|
31
|
+
3. **Producer** - Extends ``BaseClockDrivenProducer`` and implements ``_reset_state`` and ``_produce``
|
|
32
|
+
|
|
33
|
+
Example: Sine Wave Generator
|
|
34
|
+
============================
|
|
35
|
+
|
|
36
|
+
Here's a complete example of a sine wave generator:
|
|
37
|
+
|
|
38
|
+
.. code-block:: python
|
|
39
|
+
|
|
40
|
+
import numpy as np
|
|
41
|
+
from ezmsg.util.messages.axisarray import AxisArray, LinearAxis
|
|
42
|
+
|
|
43
|
+
from ezmsg.baseproc import (
|
|
44
|
+
BaseClockDrivenProducer,
|
|
45
|
+
BaseClockDrivenProducerUnit,
|
|
46
|
+
ClockDrivenSettings,
|
|
47
|
+
ClockDrivenState,
|
|
48
|
+
processor_state,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class SinGeneratorSettings(ClockDrivenSettings):
|
|
53
|
+
"""
|
|
54
|
+
Settings for the sine wave generator.
|
|
55
|
+
|
|
56
|
+
Inherits from ClockDrivenSettings which provides:
|
|
57
|
+
- fs: Output sampling rate in Hz
|
|
58
|
+
- n_time: Samples per block (optional, derived from clock if None)
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
freq: float = 1.0
|
|
62
|
+
"""Frequency of the sine wave in Hz."""
|
|
63
|
+
|
|
64
|
+
amp: float = 1.0
|
|
65
|
+
"""Amplitude of the sine wave."""
|
|
66
|
+
|
|
67
|
+
phase: float = 0.0
|
|
68
|
+
"""Initial phase in radians."""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@processor_state
|
|
72
|
+
class SinGeneratorState(ClockDrivenState):
|
|
73
|
+
"""
|
|
74
|
+
State for the sine wave generator.
|
|
75
|
+
|
|
76
|
+
Inherits from ClockDrivenState which provides:
|
|
77
|
+
- counter: Current sample counter (total samples produced)
|
|
78
|
+
- fractional_samples: For accumulating sub-sample timing
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
ang_freq: float = 0.0
|
|
82
|
+
"""Pre-computed angular frequency (2 * pi * freq)."""
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class SinGenerator(
|
|
86
|
+
BaseClockDrivenProducer[SinGeneratorSettings, SinGeneratorState]
|
|
87
|
+
):
|
|
88
|
+
"""
|
|
89
|
+
Generates sine wave data synchronized to clock ticks.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def _reset_state(self, time_axis: LinearAxis) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Initialize state. Called once before first production.
|
|
95
|
+
|
|
96
|
+
Use this to pre-compute values that don't change between chunks.
|
|
97
|
+
"""
|
|
98
|
+
self._state.ang_freq = 2 * np.pi * self.settings.freq
|
|
99
|
+
|
|
100
|
+
def _produce(self, n_samples: int, time_axis: LinearAxis) -> AxisArray:
|
|
101
|
+
"""
|
|
102
|
+
Generate sine wave data for this chunk.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
n_samples: Number of samples to generate
|
|
106
|
+
time_axis: LinearAxis with correct offset and gain (1/fs)
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
AxisArray containing the sine wave data
|
|
110
|
+
"""
|
|
111
|
+
# Calculate time values using the internal counter
|
|
112
|
+
t = (np.arange(n_samples) + self._state.counter) * time_axis.gain
|
|
113
|
+
|
|
114
|
+
# Generate sine wave
|
|
115
|
+
data = self.settings.amp * np.sin(
|
|
116
|
+
self._state.ang_freq * t + self.settings.phase
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return AxisArray(
|
|
120
|
+
data=data,
|
|
121
|
+
dims=["time"],
|
|
122
|
+
axes={"time": time_axis},
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class SinGeneratorUnit(
|
|
127
|
+
BaseClockDrivenProducerUnit[SinGeneratorSettings, SinGenerator]
|
|
128
|
+
):
|
|
129
|
+
"""
|
|
130
|
+
ezmsg Unit wrapper for SinGenerator.
|
|
131
|
+
|
|
132
|
+
Receives clock ticks on INPUT_CLOCK and outputs AxisArray on OUTPUT_SIGNAL.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
SETTINGS = SinGeneratorSettings
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
Key Points
|
|
139
|
+
==========
|
|
140
|
+
|
|
141
|
+
**Settings inheritance**: Your settings class should extend ``ClockDrivenSettings``,
|
|
142
|
+
which provides:
|
|
143
|
+
|
|
144
|
+
- ``fs``: The output sampling rate in Hz
|
|
145
|
+
- ``n_time``: Optional fixed chunk size. If ``None``, chunk size is derived from
|
|
146
|
+
the clock's gain (``fs * clock.gain``)
|
|
147
|
+
|
|
148
|
+
**State inheritance**: Your state class should extend ``ClockDrivenState``,
|
|
149
|
+
which provides:
|
|
150
|
+
|
|
151
|
+
- ``counter``: Tracks total samples produced (use this for continuous signals)
|
|
152
|
+
- ``fractional_samples``: Accumulates sub-sample timing for accurate chunk sizes
|
|
153
|
+
|
|
154
|
+
**The _produce method**: This is where you generate data. You receive:
|
|
155
|
+
|
|
156
|
+
- ``n_samples``: How many samples to generate this chunk
|
|
157
|
+
- ``time_axis``: A ``LinearAxis`` with the correct ``offset`` and ``gain`` (1/fs)
|
|
158
|
+
|
|
159
|
+
The base class automatically:
|
|
160
|
+
|
|
161
|
+
- Computes ``n_samples`` from clock timing or settings
|
|
162
|
+
- Manages the sample counter (incremented after ``_produce`` returns)
|
|
163
|
+
- Handles fractional sample accumulation for non-integer chunk sizes
|
|
164
|
+
- Supports both fixed ``n_time`` and variable chunk modes
|
|
165
|
+
|
|
166
|
+
Using Standalone (Outside ezmsg)
|
|
167
|
+
================================
|
|
168
|
+
|
|
169
|
+
Clock-driven producers can be used standalone for testing or offline processing:
|
|
170
|
+
|
|
171
|
+
.. code-block:: python
|
|
172
|
+
|
|
173
|
+
from ezmsg.util.messages.axisarray import AxisArray
|
|
174
|
+
|
|
175
|
+
# Create the producer
|
|
176
|
+
producer = SinGenerator(SinGeneratorSettings(
|
|
177
|
+
fs=1000.0, # 1000 Hz sample rate
|
|
178
|
+
n_time=100, # 100 samples per chunk
|
|
179
|
+
freq=10.0, # 10 Hz sine wave
|
|
180
|
+
amp=1.0,
|
|
181
|
+
))
|
|
182
|
+
|
|
183
|
+
# Simulate clock ticks (LinearAxis with gain=1/dispatch_rate, offset=timestamp)
|
|
184
|
+
clock_tick = AxisArray.LinearAxis(gain=0.1, offset=0.0) # 10 Hz dispatch
|
|
185
|
+
|
|
186
|
+
# Generate data
|
|
187
|
+
result = producer(clock_tick)
|
|
188
|
+
print(f"Shape: {result.data.shape}") # (100,)
|
|
189
|
+
print(f"Sample rate: {1/result.axes['time'].gain} Hz") # 1000.0 Hz
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
Using with ezmsg
|
|
193
|
+
================
|
|
194
|
+
|
|
195
|
+
In an ezmsg pipeline, connect a ``Clock`` to your generator's ``INPUT_CLOCK``:
|
|
196
|
+
|
|
197
|
+
.. code-block:: python
|
|
198
|
+
|
|
199
|
+
import ezmsg.core as ez
|
|
200
|
+
from ezmsg.baseproc import Clock, ClockSettings
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class SinPipeline(ez.Collection):
|
|
204
|
+
SETTINGS = SinGeneratorSettings
|
|
205
|
+
|
|
206
|
+
CLOCK = Clock()
|
|
207
|
+
GENERATOR = SinGeneratorUnit()
|
|
208
|
+
|
|
209
|
+
def configure(self) -> None:
|
|
210
|
+
self.CLOCK.apply_settings(ClockSettings(dispatch_rate=10.0))
|
|
211
|
+
self.GENERATOR.apply_settings(self.SETTINGS)
|
|
212
|
+
|
|
213
|
+
def network(self) -> ez.NetworkDefinition:
|
|
214
|
+
return (
|
|
215
|
+
(self.CLOCK.OUTPUT_SIGNAL, self.GENERATOR.INPUT_CLOCK),
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
See Also
|
|
220
|
+
========
|
|
221
|
+
|
|
222
|
+
- :doc:`API Reference for clockdriven module <../../../api/generated/ezmsg.baseproc.clockdriven>`
|
|
223
|
+
- :doc:`stateful` - For general stateful processor patterns
|
|
224
|
+
- :doc:`unit` - For converting processors to ezmsg Units
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
```{include} ../../README.md
|
|
2
|
+
```
|
|
3
|
+
|
|
4
|
+
## Documentation
|
|
5
|
+
|
|
6
|
+
```{toctree}
|
|
7
|
+
:maxdepth: 2
|
|
8
|
+
:caption: Contents:
|
|
9
|
+
|
|
10
|
+
guides/ProcessorsBase
|
|
11
|
+
guides/how-tos/processors/content-processors
|
|
12
|
+
api/index
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Indices and tables
|
|
16
|
+
|
|
17
|
+
- {ref}`genindex`
|
|
18
|
+
- {ref}`modindex`
|
|
@@ -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
|
|
32
|
-
__version_tuple__ = version_tuple = (1,
|
|
31
|
+
__version__ = version = '1.2.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 2, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|