1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
|
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
"""
Base I/O registry
"""
# pylint: disable=invalid-name # Allows short reference names like x, y, ...
from __future__ import annotations
import abc
import os.path as osp
from typing import Sequence
import numpy as np
from sigima.config import _
from sigima.io.base import BaseIORegistry, FormatBase
from sigima.objects.signal import SignalObj, create_signal
from sigima.worker import CallbackWorkerProtocol
class SignalIORegistry(BaseIORegistry):
"""Metaclass for registering signal I/O handler classes"""
REGISTRY_INFO: str = _("Signal I/O formats")
_io_format_instances: list[SignalFormatBase] = []
class SignalFormatBaseMeta(SignalIORegistry, abc.ABCMeta):
"""Mixed metaclass to avoid conflicts"""
class SignalFormatBase(abc.ABC, FormatBase, metaclass=SignalFormatBaseMeta):
"""Class representing a signal file type"""
HEADER_KEY = "header"
@staticmethod
def create_object(filename: str, index: int | None = None) -> SignalObj:
"""Create empty object
Args:
filename: File name
index: Index of object in file
Returns:
Signal object
"""
name = osp.basename(filename)
if index is not None:
name += f" {index:02d}"
return create_signal(name, metadata={"source": filename})
def create_signal(
self, xydata: np.ndarray, filename: str, index: int | None = None
) -> SignalObj:
"""Create signal object from xydata and filename.
Args:
xydata: XY data
filename: File name
index: Index of object in file
Returns:
Signal object
"""
obj = self.create_object(filename, index=index)
obj.set_xydata(xydata[:, 0], xydata[:, index or 1])
return obj
def create_signals_from(self, xydata: np.ndarray, filename: str) -> list[SignalObj]:
"""Create signal objects from xydata and filename
Args:
xydata: XY data
filename: File name
Returns:
List of signal objects
"""
assert isinstance(xydata, np.ndarray), "Data type not supported"
assert len(xydata.shape) in (1, 2), "Data not supported"
if len(xydata.shape) == 1:
# 1D data
obj = self.create_object(filename)
obj.set_xydata(np.arange(xydata.size), xydata)
return [obj]
# 2D data: x, y1, y2, ...
# Eventually transpose data:
if xydata.shape[1] > xydata.shape[0]:
xydata = xydata.T
# If only data contains one x and y columns, return single object without index
# in title
if xydata.shape[1] == 2:
return [self.create_signal(xydata, filename)]
objs = []
# Create objects for each y column
for i in range(1, xydata.shape[1]):
objs.append(self.create_signal(xydata, filename, i))
return objs
def read(
self, filename: str, worker: CallbackWorkerProtocol | None = None
) -> Sequence[SignalObj]:
"""Read list of signal objects from file
Args:
filename: File name
worker: Callback worker object
Returns:
List of signal objects
"""
xydata = self.read_xydata(filename)
return self.create_signals_from(xydata, filename)
def read_xydata(self, filename: str) -> np.ndarray:
"""Read data and metadata from file, write metadata to object, return xydata
Args:
filename: File name
Returns:
XY data
"""
raise NotImplementedError(f"Reading from {self.info.name} is not supported")
|