Source code for vivyd.data.signal
from __future__ import annotations
from quantities import UnitQuantity, Quantity
from dataclasses import dataclass
from numpy.typing import NDArray
from numpy import asarray
from uuid import uuid4
from zarr import Group, open_array
from zarr.storage import ZipStore
from typing import Any
[docs]
@dataclass(kw_only=True)
class Signal:
"""
A class representing a signal, which is a time series of data points.
Parameters
----------
name : str
The name of the signal.
time : NDArray
The time points of the signal.
data : NDArray
The data points of the signal. The first dimension should correspond
to time, and the remaining dimensions can be used for multi-dimensional
signals (e.g., multi-channel recordings).
units : UnitQuantity | Quantity
The units of the value, as defined by the ``quantities`` library.
"""
name: str
data: NDArray
units: UnitQuantity | Quantity
time: NDArray
def __post_init__(self):
if self.time.ndim != 1:
raise ValueError(f"Time array must be 1-dimensional. Got {self.time.ndim} dimensions.")
if self.data.shape[0] != len(self.time):
raise ValueError(f"Data and time arrays must have the same length. Got {self.data.shape[0]} and {len(self.time)}.")
def _save_arrays(self, root: Group) -> tuple[str, str]:
"""Save arrays to Zarr and return their paths."""
uid = str(uuid4())
data_path = f"arrays/{uid}/data"
time_path = f"arrays/{uid}/time"
# Create zarr arrays via group methods with chunking
# Convert to numpy in case they're already zarr arrays
data_arr = asarray(self.data)
time_arr = asarray(self.time)
root.create_array(
data_path,
data = data_arr,
chunks = (min(data_arr.shape[0], 1_000_000),),
)
root.create_array(
time_path,
data = time_arr,
chunks = (min(time_arr.shape[0], 1_000_000),),
)
return data_path, time_path
@property
def __dict__(self) -> dict[str, Any]:
return {
"name" : self.name,
"type" : self.__class__.__name__,
"units": self.units.dimensionality.string,
}
@staticmethod
def _from_dict(data: dict[str, Any], store: ZipStore) -> Signal:
data_zarr = open_array(store, path=data["data_ref"], mode="r")
time_zarr = open_array(store, path=data["time_ref"], mode="r")
return Signal(
name = data["name"],
units = Quantity(1.0, data["units"]).units,
data = asarray(data_zarr),
time = asarray(time_zarr),
)
[docs]
def tree(self, indent: int = 2) -> str:
"""
Parameters
----------
indent : int, optional
The number of spaces to indent the tree representation of the signal.
Default is 2.
Returns
-------
str
The tree representation of the signal.
"""
type_ = self.__class__.__name__
units_ = self.units.dimensionality.string
shape_ = self.data.shape
return f"{' ' * indent}<{type_}> {self.name} [{units_}] {shape_}"