Files
controller-wisetopdataserver/python/biopro/recording/data.py
T
2021-12-20 14:52:55 +08:00

211 lines
6.6 KiB
Python

"""It is typing used of the abstract interface of RecordingData defined in c extension.
**RecordingData format**
::
| | 1 | 2 | 3 |
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
-----------------------------------------------------------------
| format | device | channel | data width |
| time stamp |
| time stamp |
| ------------------------------------------------------------- |
| data | format = 0
| ------------------------------------------------------------- | -------------------
| data length | sampling rate | format = 1
| data[*] | * data length
| ------------------------------------------------------------- | -------------------
| data length | sampling rate | format = 2
| channel | data[*] | * data length
| ------------------------------------------------------------- | -------------------
| data length | sampling rate | format = 3
| invalid map | channel | data[8] | * data length
| ------------------------------------------------------------- | -------------------
data length
data length.
sampling rate
reciprocal of time delta (s) between each data.
0 meaning all data with same time stamp
format
data format.
time stamp
64 bits double, unit 1 msec
data length
type uint16 little endian, count of data
data width
data width in bytes
data
signed little endian integer.
invalid map
bit map, indicate next 8 data valid or not.
"""
from typing import Iterable, Tuple, Optional, IO, List
# noinspection PyUnresolvedReferences
import biopro.ext.recording as util_recording
__all__ = ['RecordingData']
inf = float("inf")
_RUNTIME_COMPILE = True
if _RUNTIME_COMPILE:
RecordingData = util_recording.RecordingData
else:
class RecordingData:
# XXX 1 byte channel may not fit the CV mode EliteZM,
# which channel also used to save the cycle number.
__slots__ = ('__impl', '__mod_count', '__mod_encode', '__encode_cache')
def __init__(self,
device: int,
time_stamp: float = 0,
sample_rate: int = 1):
"""new a RecordingData
:param device:
:param time_stamp: unit ms
:param sample_rate: unit 1/s
:return:
"""
if isinstance(device, int):
self.__impl: 'RecordingData' = util_recording.RecordingData(device, time_stamp, sample_rate)
else:
# internal call to wrap util_recording.RecordingData
# noinspection PyTypeChecker
self.__impl: 'RecordingData' = device
# encode() cache
# in commit eaa98054, I had implemented it in C, but it cause SIGSEGV,
# and I have no idea why.
self.__mod_count = 0
self.__mod_encode = 0
self.__encode_cache: Optional[bytes] = None
def __len__(self) -> int:
"""data count in this record.
if *format* equal 0, len return 1 iff data exist and valid.
otherwise, len return the number of the data no matter valid or invalid.
"""
return len(self.__impl)
def __getitem__(self, item: int) -> Tuple[int, Optional[int]]:
return self.__impl[item]
@property
def device(self) -> int:
return self.__impl.device
@property
def time_stamp(self) -> int:
"""
:return: time stamp relative to beginning. unit msec
"""
return self.__impl.time_stamp
@time_stamp.setter
def time_stamp(self, value: int):
self.__impl.time_stamp = value
@property
def data_width(self) -> int:
return self.__impl.data_width
@property
def data_capacity(self) -> int:
return self.__impl.data_capacity
@property
def data_size(self) -> int:
return self.__impl.data_size
@property
def to_time(self) -> int:
return self.__impl.to_time
@property
def sample_rate(self) -> int:
return self.__impl.sample_rate
@sample_rate.setter
def sample_rate(self, value: int):
self.__impl.sample_rate = value
def clear(self):
self.__impl.clear()
def channels(self) -> List[int]:
return self.__impl.channels()
def in_time_range(self, time_start: float, time_stop: float) -> bool:
return self.__impl.in_time_range(time_start, time_stop)
def append_data(self, channel: int, value: Optional[int]):
"""append data.
:param channel:
:param value: data value, None for invalid data
"""
self.__impl.append_data(channel, value)
self.__mod_count += 1
def encode(self) -> bytes:
if self.__encode_cache is not None:
if self.__mod_encode == self.__mod_count:
return self.__encode_cache
ret = self.__impl.encode()
self.__encode_cache = ret
self.__mod_encode = self.__mod_count
return ret
@staticmethod
def decode(data: bytes) -> 'RecordingData':
ret = RecordingData(util_recording.RecordingData.decode(data))
# set encode cache
ret.__encode_cache = data
ret.__mod_encode = ret.__mod_count
return ret
@staticmethod
def read_block(file: IO) -> Optional['RecordingData']:
ret = util_recording.RecordingData.read_block(file)
if ret is None:
return None
else:
return RecordingData(ret)
def __iter__(self) -> Iterable[Tuple[float, int, Optional[int]]]:
"""same as entry_iter(0)"""
return iter(self.__impl)
def entry_iter(self,
time_start: float = 0.0,
time_stop: float = inf) -> Iterable[Tuple[float, int, Optional[int]]]:
"""iterator data in this recording data"""
return self.__impl.entry_iter(time_start, time_stop)