Source code for ezmsg.sigproc.util.message

"""Message (AxisArray) utilities.

Also re-exports sample-message symbols from ezmsg.baseproc.util.message for
backwards compatibility; new code should import those directly from
ezmsg.baseproc instead.
"""

import typing

from ezmsg.baseproc.util.message import (
    SampleMessage,
    SampleTriggerMessage,
    is_sample_message,
)
from ezmsg.util.messages.axisarray import AxisArray

__all__ = [
    "SampleMessage",
    "SampleTriggerMessage",
    "has_samples_along",
    "is_empty_along",
    "is_sample_message",
    "with_fingerprint",
]


[docs] def with_fingerprint(axis: AxisArray.CoordinateAxis) -> AxisArray.CoordinateAxis: """Compute *axis*'s fingerprint now, and return the axis. Every stateful consumer reads the fingerprint of the coordinate axes that describe a stream's configuration, and the value is cached on the instance and pickled with it. Computing it at the point of construction therefore pays the checksum once, for everybody: * In this process, the axis object is reused for the life of the stream, so one call covers every message and every consumer downstream of it. * Across a process boundary it is better than that. Unpickling hands out a *new* axis object per message, so a cold axis is re-checksummed by the first consumer in every receiving process, on every message, forever. A primed one arrives with the answer already attached. Apply it to axes that describe the stream -- channel labels, frequency labels, feature labels -- not to per-message coordinates along the stream dimension, whose fingerprint no consumer reads and whose data is new every message anyway. """ axis.fingerprint # noqa: B018 -- evaluated for the caching side effect return axis
[docs] def is_empty_along(message: AxisArray, dims: typing.Iterable[str]) -> bool: """True iff any of the named dims is present in ``message`` with zero length. Publish gates use this instead of ``data.size == 0`` so a message that is empty only along *other* axes — e.g. an upstream selection removed every channel while time samples remain — still flows downstream, preserving the stream's cadence for consumers that align or merge multiple sources. Dims not present in the message are ignored. """ return any(d in message.dims and message.data.shape[message.get_axis_idx(d)] == 0 for d in dims)
[docs] def has_samples_along(message: AxisArray, dim: str) -> bool: """True iff ``dim`` is present in ``message`` with nonzero length. Stricter than ``not is_empty_along(...)``: the dim must exist. Drain loops use this to decide whether a chunk is real output, so that a placeholder lacking the axis entirely (e.g. ResampleProcessor's pre-init null template, ``dims=[""]``) counts as "nothing ready" rather than a publishable chunk. """ return dim in message.dims and message.data.shape[message.get_axis_idx(dim)] > 0