ezmsg-org / ezmsg

Pure-Python DAG-based high-performance SHM-backed pub-sub and multi-processing pattern
https://ezmsg.readthedocs.io/en/latest/
MIT License
16 stars 6 forks source link

[REQ] EventMessage class for generic events #149

Open cboulay opened 2 months ago

cboulay commented 2 months ago

Currently ezmsg-sigproc has SampleTriggerMessage.

@dataclass(unsafe_hash=True)
class SampleTriggerMessage:
    timestamp: float = field(default_factory=time.time)
    period: Optional[Tuple[float, float]] = None
    value: Any = None

I think we need something different to represent signal events, especially signal spikes like neural firing, heart beats, etc. The reason I don't like SampleTriggerMessage is because it has a pair of floats that are optional, and it has a value payload that can be anything (incl. None), thus it is difficult / slow to convert many instances of this struct to e.g. a sparse array.

I haven't put too much thought into it, but I'm thinking something like the following:

class EventMessage:
    timestamp: float
    ch_idx: int
    sub_idx: int  # e.g., 'unit id'
    value: numeric

When a graph has a source that generates events like this, the next step might be a node to accumulate a bunch of events in a given window and convert it to a sparse matrix (e.g., scipy.sparse.csr) with a given sampling rate. Then, assuming we have nodes compatible with sparse matrices, we can convolve with some kernel to get event rate.

As support for event processing develops, I think there will be some mix of making ezmsg-sigproc nodes compatible with sparse matrices, and making custom nodes.

I guess my first question for the team is where do we want to define EventMessage? Here? ezmsg-sigproc? Or a new package called ezmsg-event that will contain other utilities for dealing with events and sparse matrices?

My only objection to putting the message structure in ezmsg-event is that data source modules that can provide events (e.g., ezmsg-blackrock) shouldn't have scipy as a transient dependency.

griffinmilsap commented 3 weeks ago

SampleTriggerMessage was pretty purpose-made for that and only that unit, but I'm very on board with making a base-event class. ez.Flag is also an event when you think about it this way. I support a PR to bring EventMessage into ezmsg.util.messages so that we can share these messages across multiple extensions.

Could possibly support some number of base-classes? Current ezmsg-event EventMessage looks like:

@dataclass
class EventMessage:
    offset: float
    """The temporal offset at which the event occurred. This is a float in seconds. The reference point is
    unspecified and depends on the clock the application uses. Most applications will use time.time."""

    ch_idx: int

    sub_idx: int = 0
    """The sub-index of the channel. For Blackrock multi-unit data: 0=unsorted, 1-5 sorted unit, >5=noise"""

    value: Number = 1
    """The value of the event. This can be any number, but is usually an integer, and is often 1 for spikes."""

To bring this into ezmsg.util, I could see making it much more general. Existing EventMessage renamed to SpikeMessage because it really seems to deal with spikes, documentation making explicit reference to Blackrock NSP...

The EventMessage that could be rolled into core could just have the offset and value fields, with a default-value for offset = 0. ch_idx and sub_idx could be additionally defined in the SpikeMessage subclass. Only forseeable problem is that defining default values for a base-class, all derived dataclasses would need default values for all parameters as well (which is a known issue/bummer re: dataclasses).

cboulay commented 3 weeks ago

At the moment, most of my ezmsg-event needs are being served by AxisArray with a pydata sparse array in the .data field. So solving EventMessage has decreased priority for me for now.