547 lines
15 KiB
Python
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()
|