235 lines
7.0 KiB
Python
235 lines
7.0 KiB
Python
from enum import IntEnum
|
|
from pathlib import Path
|
|
from typing import Optional, Union, List, Tuple, Any
|
|
|
|
# noinspection PyUnresolvedReferences
|
|
import biopro.ext.message as util_message
|
|
|
|
from biopro.recording import RecordingMetaFile, RecordingData
|
|
from biopro.util.channel import ChannelMask
|
|
|
|
__all__ = ['DataPresentMode', 'DataMessageEncoder', 'RecordingFileReader']
|
|
|
|
inf = float('inf')
|
|
|
|
|
|
class DataPresentMode(IntEnum):
|
|
RAW = 0
|
|
FIRST = 1
|
|
AVERAGE = 2
|
|
|
|
# XXX not supported current
|
|
AVERAGE_BOUNDARY = 3
|
|
|
|
|
|
class DataMessageEncoder:
|
|
"""It is typing used of the abstract interface of DataMessageEncoder defined in c extension.
|
|
|
|
::
|
|
|
|
| | 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
|
|
-----------------------------------------------------------------
|
|
| header | device ID | sample rate |
|
|
| ch_count |
|
|
| channel | width | mode | data length |
|
|
| time stamp |
|
|
| ------------------------------------------------------------- |
|
|
| index | data[*] | mode == FIRST|AVERAGE
|
|
| ------------------------------------------------------------- |
|
|
| index | data[*] | mode == AVERAGE_BOUNDARY
|
|
| min % | max % |
|
|
| ------------------------------------------------------------- |
|
|
| channel' | width | mode | data length | next data section
|
|
| ------------------------------------------------------------- |
|
|
| header=0x03 | device' ID | sample rate' | next device section
|
|
| ------------------------------------------------------------- |
|
|
|
|
header
|
|
message header, value > 0
|
|
|
|
data length
|
|
little endian
|
|
|
|
time stamp
|
|
msec, little endian
|
|
|
|
sample rate
|
|
unit 1/s, little endian
|
|
|
|
ch_count
|
|
channel count
|
|
|
|
width
|
|
data width, 2 or 4 bytes
|
|
|
|
mode
|
|
data present mode
|
|
|
|
index
|
|
time_step. always increase. may overflow
|
|
|
|
data
|
|
unsigned little endian int. actually value equal to data - 0x8000
|
|
|
|
"""
|
|
|
|
__slots__ = '__impl', '__mod_count', '__mod_message', '__message_cache'
|
|
|
|
def __init__(self,
|
|
header: int,
|
|
mode: DataPresentMode,
|
|
sample_rate: int,
|
|
device: int,
|
|
channel: Union[int, List[int], ChannelMask]):
|
|
"""
|
|
|
|
:param header: data message header
|
|
:param mode: data present mode
|
|
:param sample_rate: sample rate in unit 1/sec
|
|
:param device: listen device ID
|
|
:param channel: listen channel or channel mask
|
|
:return: data message encoder
|
|
"""
|
|
if sample_rate < 1:
|
|
raise ValueError('sample rate less then 1 : ' + str(sample_rate))
|
|
|
|
if isinstance(channel, int):
|
|
channel = [channel]
|
|
|
|
self.__impl: 'DataMessageEncoder' = util_message.DataMessageEncoder(header,
|
|
mode.value,
|
|
sample_rate,
|
|
device,
|
|
channel)
|
|
|
|
# message() cache
|
|
self.__mod_count = 0
|
|
self.__mod_message = 0
|
|
self.__message_cache: Optional[bytes] = None
|
|
|
|
@property
|
|
def header(self) -> int:
|
|
return self.__impl.header
|
|
|
|
@property
|
|
def width(self) -> int:
|
|
return self.__impl.width
|
|
|
|
@property
|
|
def mode(self) -> int:
|
|
return self.__impl.mode
|
|
|
|
@mode.setter
|
|
def mode(self, value: int):
|
|
self.__impl.mode = value
|
|
|
|
@property
|
|
def sample_rate(self) -> float:
|
|
return self.__impl.sample_rate
|
|
|
|
@property
|
|
def device(self) -> int:
|
|
return self.__impl.device
|
|
|
|
def channels(self) -> List[int]:
|
|
return self.__impl.channels()
|
|
|
|
def contain_channel(self, channel: int) -> bool:
|
|
return self.__impl.contain_channel(channel)
|
|
|
|
def append_entry(self, time_stamp: float, channel: int, value: int):
|
|
"""
|
|
|
|
:param time_stamp: time stamp in unit msec
|
|
:param channel: channel ID
|
|
:param value: data value
|
|
"""
|
|
self.__impl.append_entry(time_stamp, channel, value)
|
|
self.__mod_count += 1
|
|
|
|
def append_data(self, data: RecordingData, time_start: float = 0.0, time_stop: float = inf):
|
|
self.__impl.append_data(data, time_start, time_stop)
|
|
self.__mod_count += 1
|
|
|
|
def append_file(self, file: 'RecordingFileReader', time_start: float = 0.0, time_stop: float = inf):
|
|
self.__impl.append_file(file._impl, time_start, time_stop)
|
|
self.__mod_count += 1
|
|
|
|
def message(self) -> Optional[bytes]:
|
|
if self.__message_cache is not None:
|
|
if self.__mod_message == self.__mod_count:
|
|
return self.__message_cache
|
|
|
|
ret = self.__impl.message()
|
|
|
|
self.__message_cache = ret
|
|
self.__mod_message = self.__mod_count
|
|
|
|
return ret
|
|
|
|
|
|
class RecordingFileReader:
|
|
"""It is typing used of the warp class for RecordingFileReader defined in c extension.
|
|
"""
|
|
|
|
__slots__ = ('_impl',)
|
|
|
|
def __init__(self, meta: Union[RecordingMetaFile, Path, str]):
|
|
if isinstance(meta, RecordingMetaFile):
|
|
path = str(meta.filepath)
|
|
else:
|
|
path = str(meta)
|
|
|
|
self._impl: 'RecordingFileReader' = util_message.RecordingFileReader(path.encode())
|
|
|
|
@property
|
|
def file_path(self) -> bytes:
|
|
return self._impl.file_path
|
|
|
|
@property
|
|
def version_number(self) -> int:
|
|
return self._impl.version_number
|
|
|
|
@property
|
|
def create_time(self) -> int:
|
|
return self._impl.create_time
|
|
|
|
@property
|
|
def file_uuid(self) -> bytes:
|
|
return self._impl.file_uuid
|
|
|
|
@property
|
|
def data_format(self) -> bytes:
|
|
return self._impl.data_format
|
|
|
|
@property
|
|
def device_name(self) -> bytes:
|
|
return self._impl.device_name
|
|
|
|
@property
|
|
def device_address(self) -> bytes:
|
|
return self._impl.device_address
|
|
|
|
@property
|
|
def device_serial(self) -> bytes:
|
|
return self._impl.device_serial
|
|
|
|
@property
|
|
def parameter_count(self) -> int:
|
|
return self._impl.parameter_count
|
|
|
|
def parameter(self, index: int) -> Tuple[str, Any]:
|
|
return self._impl.parameter(index)
|
|
|
|
@property
|
|
def recording_file_size(self) -> int:
|
|
return self._impl.recording_file_size
|
|
|
|
def recording_file_name(self, file_index: int) -> bytes:
|
|
return self._impl.recording_file_name(file_index)
|
|
|
|
def invalid_cache(self):
|
|
# TODO not implement in C extension
|
|
pass
|