217 lines
5.7 KiB
Python
217 lines
5.7 KiB
Python
import uuid
|
|
from enum import IntEnum, unique
|
|
|
|
from biopro.devlib.device import DeviceResponseInfo, DeviceSerialNumber
|
|
from biopro.devlib.encoder import *
|
|
from biopro.util.channel import ChannelMask
|
|
|
|
META_MAGIC_NUMBER = b'BPRM'
|
|
FILE_MAGIC_NUMBER = b'BPRF'
|
|
|
|
|
|
@unique
|
|
class RecordingFileVersion(IntEnum):
|
|
"""Recording file version table."""
|
|
|
|
VERSION_00_01 = 0x0000
|
|
"""nothing"""
|
|
|
|
VERSION_00_02 = 0x0001
|
|
"""introduce recording file format"""
|
|
|
|
__slots__ = ()
|
|
|
|
|
|
class RecordingFileDataFormat:
|
|
RAW_DATA = b'raw\0'
|
|
REC_DATA = b'rec\0'
|
|
|
|
DATA = (RAW_DATA, REC_DATA)
|
|
|
|
__slots__ = ()
|
|
|
|
|
|
class RecordingMetaFileLoader(metaclass=abc.ABCMeta):
|
|
__slots__ = ('header', 'version', 'create_time', 'last_time', 'file_uuid', 'data_format',
|
|
'device_response', 'configuration', 'channel_mask', 'recording_file_ch')
|
|
|
|
def __init__(self):
|
|
# fields
|
|
self.header = None
|
|
self.version = None
|
|
self.create_time = None
|
|
self.last_time = None
|
|
self.file_uuid = None
|
|
self.data_format = None
|
|
self.device_response = None
|
|
self.configuration = None
|
|
self.channel_mask = None
|
|
# self.recording_file = []
|
|
self.recording_file_ch = []
|
|
# self.recording_file_start_time = []
|
|
|
|
@classmethod
|
|
def load(cls, filepath: Path) -> 'RecordingMetaFileLoader':
|
|
"""load meta file
|
|
|
|
:param filepath: file path
|
|
:return: meta file
|
|
:raises IOError: not a meta file header, or unsupported version
|
|
:raises FileNotFoundError:
|
|
"""
|
|
try:
|
|
with filepath.open('rb') as _f:
|
|
f = FileDecoder(_f)
|
|
|
|
# magic number
|
|
header = f.read(4)
|
|
|
|
if header != META_MAGIC_NUMBER:
|
|
raise IOError('not a BioPro Recording File : ' + str(filepath))
|
|
|
|
# version number
|
|
try:
|
|
version = f.u32()
|
|
version = RecordingFileVersion(version)
|
|
except ValueError as e:
|
|
raise IOError('unsupported BioPro Recording File version : ' + str(version)) from e
|
|
|
|
# get loader
|
|
loader = cls.loader(version)
|
|
|
|
# init fields
|
|
loader.header = header
|
|
loader.version = version
|
|
|
|
# load left content
|
|
loader.load_content(f)
|
|
|
|
return loader
|
|
except IOError as e:
|
|
raise IOError('load meta file : ' + ' '.join(e.args)) from e
|
|
|
|
@classmethod
|
|
def loader(cls, version: RecordingFileVersion) -> 'RecordingMetaFileLoader':
|
|
if version == RecordingFileVersion.VERSION_00_02:
|
|
return DefaultRecordingMetaFileLoader()
|
|
|
|
else:
|
|
raise RuntimeError('illegal recording meta file version : ' + str(version))
|
|
|
|
@abc.abstractmethod
|
|
def load_content(self, f: FileDecoder):
|
|
pass
|
|
|
|
|
|
# noinspection PyMethodMayBeStatic
|
|
class DefaultRecordingMetaFileLoader(RecordingMetaFileLoader):
|
|
__slots__ = ()
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
def load_content(self, f: FileDecoder):
|
|
"""load meta file content
|
|
|
|
:param f: file stream
|
|
:return: device configurations
|
|
:raises IOError:
|
|
"""
|
|
|
|
# create time
|
|
self.create_time = f.u64()
|
|
|
|
# last time
|
|
self.last_time = f.u64()
|
|
|
|
# file uuid
|
|
self.file_uuid = uuid.UUID(bytes=f.read(16))
|
|
|
|
# data format
|
|
data_format = f.read(4)
|
|
if data_format not in RecordingFileDataFormat.DATA:
|
|
raise IndexError('unknown data format : ' + repr(data_format))
|
|
else:
|
|
self.data_format = data_format
|
|
|
|
# recording device
|
|
self.device_response = self.load_device_response(f)
|
|
|
|
# recording parameter
|
|
self.configuration = self.load_configuration(f)
|
|
|
|
# channel information
|
|
self.channel_mask = self.load_channel(f)
|
|
|
|
# recording file list
|
|
# self.recording_file.clear()
|
|
self.recording_file_ch.clear()
|
|
|
|
self.load_recording_files(f)
|
|
|
|
|
|
|
|
def load_device_response(self, f: FileDecoder) -> str:
|
|
device = f.decode_string()
|
|
|
|
# address = f.decode_string()
|
|
# serial = f.u8a(6)
|
|
|
|
return device
|
|
|
|
def load_configuration(self, f: FileDecoder) -> DeviceConfiguration:
|
|
"""load configuration
|
|
|
|
:param f: file stream
|
|
:return: device configuration
|
|
:raises IOError:
|
|
"""
|
|
try:
|
|
return DeviceConfigurationDecoder.decode(f)
|
|
except (IOError, UnicodeDecodeError, EOFError, ValueError) as e:
|
|
raise IOError('load configuration : ' + str(e)) from e
|
|
|
|
def load_channel(self, f: FileDecoder) -> ChannelMask:
|
|
ret = ChannelMask()
|
|
|
|
size = f.u8()
|
|
ret.add_channel(f.u8a(size))
|
|
|
|
return ret
|
|
|
|
def load_recording_files(self, f: FileDecoder):
|
|
# while True:
|
|
# data = self.load_recording_file_data(f)
|
|
|
|
# if data is None or data == 'channel_level':
|
|
# break
|
|
|
|
# else:
|
|
# self.recording_file.append(data)
|
|
|
|
while True:
|
|
data = self.load_recording_file_data(f)
|
|
|
|
if data is None:
|
|
break
|
|
|
|
else:
|
|
self.recording_file_ch.append(data)
|
|
|
|
# while True:
|
|
# data = self.load_recording_file_data(f)
|
|
|
|
# if data is None:
|
|
# break
|
|
|
|
# else:
|
|
# self.recording_file_start_time.append(data)
|
|
|
|
def load_recording_file_data(self, f: FileDecoder) -> Optional[str]:
|
|
name = f.decode_string()
|
|
|
|
if len(name) == 0:
|
|
return None
|
|
else:
|
|
return name
|