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

547 lines
15 KiB
Python

import sys
from collections import Counter
from itertools import groupby
from typing import Tuple
from biopro.util.cli import *
from biopro.util.iter import counter_total
from biopro.util.text import list_padding, Table
from .file import *
# from .tar import compress_meta_file, extra_meta_file
inf = float("inf")
# noinspection PyUnusedLocal
class Main(CliMain):
def __init__(self):
super().__init__()
self.root_path: Path = Path('.').absolute()
@cli_flags('-h', '--help', force_return=True)
def _help(self, opt: str):
"""print help document"""
self.print_help()
@cli_options('--root', value='PATH')
def _root_path(self, opt, value):
"""storage root. default current directory."""
self.root_path = Path(value).absolute()
@cli_command('help')
def _help_command(self, cmd: str, argv: List[str]):
"""print action help"""
if len(argv) == 0:
self._help('')
elif argv[0] in ('help', '-h', '--help'):
print(sys.argv[0], 'help', 'COMMAND')
else:
self.parsing([argv[0], '-h'])
@cli_command('meta')
def _meta_command(self, act, argv: List[str]):
"""print recording meta file."""
return MainMeta(act, self.root_path)
@cli_command('list')
def _list_command(self, act, argv: List[str]):
"""list recording files."""
return _MainList(act, self.root_path)
@cli_command('info')
def _info_command(self, act, argv: List[str]):
"""print header of the recording file."""
return MainInfo(act, self.root_path)
@cli_command('print')
def _print_command(self, act, argv: List[str]):
"""output data content of the recording file."""
return _MainPrint(act, self.root_path)
@cli_command('rename')
def _rename_command(self, act, argv: List[str]):
"""rename recording files"""
return MainRename(act, self.root_path)
@cli_command('remove')
def _remove_command(self, act, argv: List[str]):
"""remove recording files"""
return MainRemove(act, self.root_path)
@cli_command('tar')
def _tar_command(self, act, argv: List[str]):
"""extra/compress recording files"""
return MainTar(act, self.root_path)
@cli_command('analysis')
def _analysis_command(self, act, argv: List[str]):
"""analysis the data missing of recording file"""
return _MainAnalysis(act, self.root_path)
def run(self):
self._help('')
# noinspection PyUnusedLocal
class _RecordingSubCommand(CliSubCommandMain, metaclass=abc.ABCMeta):
def __init__(self, command: str, root_path: Path):
super().__init__(command)
self.root: Path = root_path
@cli_flags('-h', '--help', force_return=True)
def _help(self, opt: str):
"""print help document"""
self.print_help()
def _depath(self, path: Path) -> str:
try:
return str(path.relative_to(self.root))
except ValueError:
return str(path)
# noinspection PyUnusedLocal
class MainMeta(_RecordingSubCommand):
def __init__(self, command: str, root_path: Path):
super().__init__(command, root_path)
self.meta_file: str = None
self._mata_cache: Optional[RecordingMetaFile] = None
@cli_arguments(0, 'FILE')
def _meta_file(self, pos: int, value: str):
"""recording meta file"""
self.meta_file = value
def run(self):
meta = self.load_meta()
if meta is not None:
print(self._depath(meta.filepath))
def load_meta(self) -> RecordingMetaFile:
if self.meta_file is None:
raise RuntimeError('lost FILE')
if self._mata_cache is not None:
return self._mata_cache
self._mata_cache = meta = self.find_meta_by_name(self.root, self.meta_file)
if meta is None:
raise FileNotFoundError('Recording meta file not found : ' + self.meta_file)
return meta
@classmethod
def find_meta_by_name(cls, root: Path, name: str) -> Optional[RecordingMetaFile]:
p = (root / name)
if p.suffix == RecordingMetaFile.FILE_EXT:
pass
elif p.suffix == RecordingFile.FILE_EXT:
# remove serial number and replace file suffix
p = p.with_suffix('').with_suffix(RecordingMetaFile.FILE_EXT)
else:
p = p.with_name(p.name + RecordingMetaFile.FILE_EXT)
if RecordingMetaFile.is_recording_meta_file(p):
return RecordingMetaFile.load(p)
return None
# noinspection PyUnusedLocal
class _MainList(_RecordingSubCommand):
def __init__(self, command: str, root_path: Path):
super().__init__(command, root_path)
self.list_path: str = None
# when PATH is directory
self.recursive = False
self.show_directory = False
# when PATH is file.
self.list_recording = False
self.list_comment = False
@cli_flags('--show-directory')
def _show_directory(self, opt):
"""when PATH is directory. print directory in the PATH"""
self.show_directory = True
@cli_flags('-R', '--recursive')
def _recursive(self, opt):
"""when PATH is directory. recursive directory"""
self.recursive = True
@cli_flags('-r', '--recording')
def _list_recording(self, opt):
"""when PATH is file, list recording files"""
self.list_recording = True
@cli_flags('-c', '--comment')
def _list_comment(self, opt):
"""when PATH is file, list comments files"""
self.list_comment = True
@cli_arguments('?', 'PATH')
def _list_path(self, pos: int, value: str):
self.list_path = value
def run(self):
if self.list_path is not None:
p = self.root / self.list_path
else:
p = self.root
if p.is_dir():
self._run_directory(p)
else:
self._run_file(p)
def _run_directory(self, directory: Path):
if self.recursive:
def _key(_p: Path) -> str:
if _p.is_dir():
return 'd'
elif _p.is_file():
return 'f'
else:
return '?'
for t, ps in groupby(directory.iterdir(), key=_key):
if t == 'f':
for p in ps:
if RecordingMetaFile.is_recording_meta_file(p):
print(self._depath(p))
elif t == 'd':
for p in ps:
if self.show_directory:
print(self._depath(p))
self._run_directory(p)
else:
for p in directory.iterdir():
if p.is_dir() and self.show_directory:
print(p.name + '/')
if RecordingMetaFile.is_recording_meta_file(p):
print(p.name)
def _run_file(self, path: Path):
meta = MainMeta.find_meta_by_name(path.parent, path.name)
if meta is None:
raise FileNotFoundError('Recording meta file not found : ' + self._depath(path))
print(self._depath(meta.filepath))
# noinspection PyUnusedLocal
class MainInfo(MainMeta):
def __init__(self, command: str, root_path: Path):
super().__init__(command, root_path)
self.show_recording = False
@cli_flags('-r', '--recording')
def _show_recording_data(self, opt):
"""show information of each recording files"""
self.show_recording = True
def run(self):
meta = self.load_meta()
try:
p = meta.filepath.relative_to(self.root)
except ValueError:
p = meta.filepath
table = Table('file_path', str(p))
table.extend(self.info(meta))
table.append(None, None)
table.extend(self.info(meta.configuration))
table.append(None, None)
table.set_format(0, align_right=True)
table.set_format(1, align_right=False)
table.print()
@classmethod
def print_info(cls, meta: RecordingMetaFile):
table = Table('file_path', str(meta.filepath))
table.extend(cls.info(meta))
table.append(None, None)
table.extend(cls.info(meta.configuration))
table.append(None, None)
table.set_format(0, align_right=True)
table.set_format(1, align_right=False)
table.print()
@staticmethod
def info(data) -> List[Union[str, Tuple[str, Any]]]:
if isinstance(data, RecordingMetaFile):
return [
('file_version', data.file_version),
('create_time_stamp', data.create_time),
('file_size', data.file_size),
('file_uuid', data.file_uuid),
('channels', data.channels),
]
elif isinstance(data, DeviceConfiguration):
return [(k, data[k]) for k in data.keys(list_hide=True)]
elif isinstance(data, RecordingFile):
block = list(data.read_block_iter())
ret = [
'# data serial_number : %d' % data.serial_number,
('file_size', data.filepath.stat().st_size),
('data_count', len(block))
]
for d in block:
ret.append(('time_stamp', d.time_stamp))
return ret
else:
raise RuntimeError('unknown data type : ' + str(type(data)))
# noinspection PyUnusedLocal
class _MainPrint(MainMeta):
def __init__(self, command: str, root_path: Path):
super().__init__(command, root_path)
self.time_start = 0.0
self.time_stop = inf
self.print_format = 'txt'
self.resample_rate: int = 0
self.resample_method: int = 0
self.continuous = False
@cli_options('-t', '--time-range', value='[START][:STOP]')
def _time_range(self, opt: str, value: str):
"""given time range"""
if ':' in value:
f, t = value.split(':', 2)
self.time_start = int(f)
self.time_stop = int(t)
else:
self.time_start = int(value)
@cli_options('-f', '--format', value='FORMAT')
def _print_format(self, opt: str, value: str):
"""print format, could be:
txt : raw text format
csv : csv format
tsv : tsv format
"""
if value not in ('txt', 'csv', 'tsv'):
raise ValueError('unknown print format : ' + value)
self.print_format = value
@cli_options('-r', '--resample-rate', value='RATE')
def _resample_rate(self, opt: str, value: str):
"""set resample rate"""
self.resample_rate = int(value)
@cli_options('-m', '--resample-method', value='METHOD')
def _resample_method(self, opt: str, value: str):
"""set resample method"""
self.resample_method = int(value)
@cli_flags('-c', '--continuous')
def _continuous(self, flag: str):
"""continuous mode"""
self.continuous = True
def run(self):
if self.print_format == 'txt':
split = ' '
elif self.print_format == 'csv':
split = ','
elif self.print_format == 'tsv':
split = '\t'
else:
raise ValueError('unknown print format : ' + self.print_format)
# noinspection PyUnusedLocal
class MainRename(MainMeta):
def __init__(self, command: str, root_path: Path):
super().__init__(command, root_path)
self.output_file: str = None
@cli_arguments(1, 'OUTPUT')
def _output_file(self, pos, value):
"""output file"""
self.output_file = value
def run(self):
if self.output_file is None:
raise RuntimeError('lost output file')
elif '/' in self.output_file:
raise RuntimeError('illegal output filename : cannot contain /')
meta = self.load_meta()
output_path = Path(self.output_file)
meta.change_filepath(output_path)
# noinspection PyUnusedLocal
class MainRemove(MainMeta):
def __init__(self, command: str, root_path: Path):
super().__init__(command, root_path)
self.keep_meta = False
@cli_flags('--keep-meta')
def _keep_meta_files(self, opt):
"""do not remove meta file"""
self.keep_meta = True
def run(self):
if self.keep_meta:
meta = self.load_meta()
meta.remove_file()
meta.write()
else:
RecordingMetaFile.remove_meta_file(self.meta_file)
# noinspection PyUnusedLocal
class MainTar(MainMeta):
def __init__(self, command: str, root_path: Path):
super().__init__(command, root_path)
self.extra_flag: Optional[str] = None
self.overwrite = False
self.delete_source = False
@cli_flags('-h', '--help', force_return=True)
def _help(self, opt: str):
"""print help document"""
self.print_help_usage('[-c|-x]', '[OPTIONS]', 'FILES')
main_doc = self.__doc__
if main_doc is not None:
print()
self.print_help_header()
print()
self.print_help_content()
if main_doc is not None:
print()
self.print_help_footer()
@cli_flags('-x', '--extra')
def _extra_meta(self, opt):
"""extra meta file"""
self.extra_flag = 'x'
@cli_flags('-c', '--compress')
def _compress_meta(self, opt):
"""compress meta file"""
self.extra_flag = 'c'
@cli_flags('--delete')
def _delete(self, opt):
"""delete meta file after compress or extra"""
self.delete_source = True
@cli_flags('--overwrite')
def _overwrite(self, opt):
"""overwrite the meta file if existed when extra file"""
self.overwrite = True
def run(self):
if self.extra_flag is None:
raise RuntimeError('lost flag: -x or -c')
elif self.extra_flag == 'x':
self.extra_file()
elif self.extra_flag == 'c':
self.compress_file()
else:
raise RuntimeError('unknown flag : ' + self.extra_flag)
# def extra_file(self):
# extra_meta_file(self.meta_file, overwrite=self.overwrite, delete_source=self.delete_source)
def compress_file(self):
compress_meta_file(self.load_meta(), delete_source=self.delete_source)
# noinspection PyUnusedLocal
class _MainAnalysis(_MainPrint):
def __init__(self, command: str, root_path: Path):
super().__init__(command, root_path)
# data
self.time_list = []
self.channel_id_counter: Counter = None
def run(self):
meta = self.load_meta()
print('channel %s' % repr(meta.configuration.channel))
# reset data
self.time_list = []
self.channel_id_counter = Counter()
self._run_table_channel_id()
def _run_table_channel_id(self):
total = counter_total(self.channel_id_counter)
print('channel total', total)
t1 = ['channel']
t2 = ['count']
t3 = ['(count%)']
for channel, count in sorted(self.channel_id_counter.items(), key=lambda it: it[0]):
t1.append(str(channel))
t2.append(str(count))
t3.append('%.2f%%' % (100 * count / total))
list_padding(t1, t2, align_right=True, split=' : ')
list_padding(t1, t3, align_right=True)
for line in t1:
print(' ', line)
print()
if __name__ == '__main__':
Main().main()