corrscope/ovgenpy/ovgenpy.py

204 wiersze
6.2 KiB
Python
Czysty Zwykły widok Historia

# -*- coding: utf-8 -*-
2018-07-14 06:54:14 +00:00
import time
from contextlib import ExitStack, contextmanager
2018-08-16 01:19:27 +00:00
from enum import unique, IntEnum
from typing import Optional, List, Union, TYPE_CHECKING
2018-07-12 22:27:26 +00:00
from ovgenpy import outputs
from ovgenpy.channel import Channel, ChannelConfig
2018-08-17 21:50:28 +00:00
from ovgenpy.config import register_config, register_enum
from ovgenpy.renderer import MatplotlibRenderer, RendererConfig, LayoutConfig
from ovgenpy.triggers import ITriggerConfig, CorrelationTriggerConfig
from ovgenpy.utils import keyword_dataclasses as dc
from ovgenpy.utils.keyword_dataclasses import field
from ovgenpy.wave import Wave
2018-07-12 22:27:26 +00:00
if TYPE_CHECKING:
from ovgenpy.triggers import CorrelationTrigger
# cgitb.enable(format='text')
PRINT_TIMESTAMP = True
2018-07-14 06:54:14 +00:00
2018-08-17 21:50:28 +00:00
@register_enum
2018-08-16 01:19:27 +00:00
@unique
class BenchmarkMode(IntEnum):
NONE = 0
TRIGGER = 1
RENDER = 2
OUTPUT = 3
2018-07-14 06:54:14 +00:00
@register_config(always_dump='begin_time')
class Config:
2018-07-25 12:25:53 +00:00
master_audio: Optional[str]
2018-07-12 22:27:26 +00:00
fps: int
begin_time: float = 0
2018-07-24 03:38:15 +00:00
subsampling: int
width_ms: int
channels: List[ChannelConfig] = field(default_factory=list)
trigger: ITriggerConfig # Can be overriden per Wave
amplification: float
layout: LayoutConfig
render: RendererConfig
outputs: List[outputs.IOutputConfig]
2018-07-13 02:29:05 +00:00
show_buffer: bool = False
2018-08-17 21:50:28 +00:00
benchmark_mode: Union[str, BenchmarkMode] = BenchmarkMode.NONE
2018-08-16 01:19:27 +00:00
@property
def render_width_s(self) -> float:
return self.width_ms / 1000
2018-07-13 02:29:05 +00:00
2018-08-16 01:19:27 +00:00
def __post_init__(self):
try:
2018-08-17 21:50:28 +00:00
if not isinstance(self.benchmark_mode, BenchmarkMode):
self.benchmark_mode = BenchmarkMode[self.benchmark_mode]
2018-08-16 01:19:27 +00:00
except KeyError:
raise ValueError(
f'invalid benchmark_mode mode {self.benchmark_mode} not in '
f'{[el.name for el in BenchmarkMode]}')
2018-07-12 22:27:26 +00:00
2018-07-13 12:37:22 +00:00
_FPS = 60 # f_s
2018-07-12 22:27:26 +00:00
def default_config(**kwargs):
2018-07-12 22:27:26 +00:00
cfg = Config(
master_audio='',
fps=_FPS,
# begin_time=0,
channels=[],
width_ms=25,
subsampling=1,
2018-08-25 23:35:15 +00:00
trigger=CorrelationTriggerConfig(),
amplification=1,
layout=LayoutConfig(ncols=1),
render=RendererConfig(1280, 720),
outputs=[
2018-07-22 12:31:49 +00:00
# outputs.FFmpegOutputConfig(output),
outputs.FFplayOutputConfig(),
]
2018-07-12 22:27:26 +00:00
)
return dc.replace(cfg, **kwargs)
2018-07-12 22:27:26 +00:00
class Ovgen:
def __init__(self, cfg: Config):
self.cfg = cfg
self.has_played = False
2018-07-20 21:19:43 +00:00
if len(self.cfg.channels) == 0:
raise ValueError('Config.channels is empty')
waves: List[Wave]
channels: List[Channel]
outputs: List[outputs.Output]
nchan: int
2018-07-12 22:27:26 +00:00
def _load_channels(self):
self.channels = [Channel(ccfg, self.cfg) for ccfg in self.cfg.channels]
self.waves = [channel.wave for channel in self.channels]
self.triggers = [channel.trigger for channel in self.channels]
self.nchan = len(self.channels)
@contextmanager
def _load_outputs(self):
with ExitStack() as stack:
self.outputs = [
stack.enter_context(output_cfg(self.cfg))
for output_cfg in self.cfg.outputs
]
yield
def _load_renderer(self):
renderer = MatplotlibRenderer(self.cfg.render, self.cfg.layout, self.nchan)
renderer.set_colors(self.cfg.channels)
return renderer
def play(self):
if self.has_played:
raise ValueError('Cannot call Ovgen.play() more than once')
self.has_played = True
self._load_channels()
2018-07-12 22:27:26 +00:00
# Calculate number of frames (TODO master file?)
fps = self.cfg.fps
begin_frame = round(fps * self.cfg.begin_time)
end_frame = fps * self.waves[0].get_s()
end_frame = int(end_frame) + 1
2018-07-12 22:27:26 +00:00
renderer = self._load_renderer()
2018-07-12 22:27:26 +00:00
# Display buffers, for debugging purposes.
show_buffer = self.cfg.show_buffer
if show_buffer:
from ovgenpy.outputs import FFplayOutputConfig
from ovgenpy.utils.keyword_dataclasses import replace
no_audio = replace(self.cfg, master_audio='')
buf_render = self._load_renderer()
buf_output = FFplayOutputConfig()(no_audio)
if PRINT_TIMESTAMP:
2018-07-13 11:05:31 +00:00
begin = time.perf_counter()
2018-08-16 01:19:27 +00:00
benchmark_mode = self.cfg.benchmark_mode
not_benchmarking = not benchmark_mode
2018-07-12 22:27:26 +00:00
# For each frame, render each wave
with self._load_outputs():
prev = -1
for frame in range(begin_frame, end_frame):
time_seconds = frame / fps
rounded = int(time_seconds)
if PRINT_TIMESTAMP and rounded != prev:
print(rounded)
prev = rounded
datas = []
# Get data from each wave
for wave, channel in zip(self.waves, self.channels):
sample = round(wave.smp_s * time_seconds)
if not_benchmarking or benchmark_mode == BenchmarkMode.TRIGGER:
trigger_sample = channel.trigger.get_trigger(sample)
else:
trigger_sample = sample
datas.append(wave.get_around(
2018-08-26 01:50:57 +00:00
trigger_sample, channel.window_samp, channel.render_subsampling))
# Display buffers, for debugging purposes.
if show_buffer:
triggers: List['CorrelationTrigger'] = self.triggers
buf_render.render_frame([trigger._buffer for trigger in triggers])
buf_output.write_frame(buf_render.get_frame())
if not_benchmarking or benchmark_mode >= BenchmarkMode.RENDER:
# Render frame
renderer.render_frame(datas)
if not_benchmarking or benchmark_mode == BenchmarkMode.OUTPUT:
# Output frame
frame = renderer.get_frame()
for output in self.outputs:
output.write_frame(frame)
if PRINT_TIMESTAMP:
# noinspection PyUnboundLocalVariable
2018-07-13 11:05:31 +00:00
dtime = time.perf_counter() - begin
render_fps = (end_frame - begin_frame) / dtime
2018-07-13 11:05:31 +00:00
print(f'FPS = {render_fps}')