blob: 64334c78ad0eaab2aadcdf2c9fd1de3bc1a51ce8 [file] [log] [blame]
# SPDX-License-Identifier: MPL-2.0
# Copyright (c) 2022 Philipp Le <philipp@philipple.de>.
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
from __future__ import annotations
from tkinter import ttk, LEFT, BOTH
from pydantic import confloat, conint
from dcs.config import default_store, ConfigObject, ui_create, ConfigControlFrame
import numpy as np
from dcs.frames.base import BaseFrame, Window
from dcs.frames.groups import Ch03Group
from typing import List
from enum import Enum
import scipy.signal
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class FunctionType(str, Enum):
Cos = 'Cosine'
Rect = 'Rectangular'
Tri = 'Triangular'
Gauss = 'Gaussian'
@ui_create
class FunctionSet(ConfigObject):
function: FunctionType = FunctionType.Gauss
amplitude: confloat(ge=0.0, lt=10.0, multiple_of=0.01) = 5.0
width: confloat(ge=0.01, lt=1.0, multiple_of=0.01) = 0.5
offset: confloat(ge=-5.0, lt=5.0, multiple_of=0.01) = 0.0
def make_config_widget(self, parent: ttk.Widget) -> ConfigControlFrame:
frm = ConfigControlFrame(parent, borderwidth=1, relief='raised')
ttk.Label(frm, text='Function:').grid(row=0, column=0)
w = self.ui_create_function_dropdown(frm)
w.grid(row=0, column=1)
frm.add_widget(w)
ttk.Label(frm, text='Amplitude:').grid(row=1, column=0)
w = self.ui_create_amplitude(frm)
w.grid(row=1, column=1)
frm.add_widget(w)
ttk.Label(frm, text='Width:').grid(row=2, column=0)
w = self.ui_create_width(frm)
w.grid(row=2, column=1)
frm.add_widget(w)
ttk.Label(frm, text='Offset:').grid(row=3, column=0)
w = self.ui_create_offset(frm)
w.grid(row=3, column=1)
frm.add_widget(w)
return frm
@ui_create
class ConfigCh03CrossCorrelation(ConfigObject):
_KEY = 'ch03_cross'
fn1: FunctionSet = FunctionSet()
fn2: FunctionSet = FunctionSet()
tau: confloat(ge=-1.0, lt=1.0, multiple_of=0.01) = 0.2
sigma: confloat(ge=0, le=3.0, multiple_of=0.01) = 0.0
random_seed: conint(ge=0, lt=10000) = 1000
def make_config_widget(self, parent: ttk.Widget) -> ConfigControlFrame:
frm = ConfigControlFrame(parent, borderwidth=1, relief='raised')
ttk.Label(frm, text='Function 1').pack()
frm_fn1 = self.fn1.make_config_widget(frm)
frm_fn1.pack()
for w in frm_fn1.ctrl_widgets:
frm.add_widget(w)
ttk.Label(frm, text='Function 2').pack()
frm_fn2 = self.fn2.make_config_widget(frm)
frm_fn2.pack()
for w in frm_fn2.ctrl_widgets:
frm.add_widget(w)
ttk.Label(frm_fn2, text='Tau (Shift):').grid(row=4, column=0)
w = self.ui_create_tau(frm_fn2)
w.grid(row=4, column=1)
frm.add_widget(w)
ttk.Label(frm_fn2, text='Standard Deviation:').grid(row=5, column=0)
w = self.ui_create_sigma(frm_fn2)
w.grid(row=5, column=1)
frm.add_widget(w)
frm_general = ttk.Frame(frm, borderwidth=1, relief='raised')
frm_general.pack()
ttk.Label(frm_general, text='Random Seed:').grid(row=1, column=0)
w = self.ui_create_random_seed(frm_general)
w.grid(row=1, column=1)
frm.add_widget(w)
return frm
def _calc_cos(self, fn: FunctionSet, t: np.ndarray, shift: float) -> np.ndarray:
phi = fn.amplitude * np.exp(1j * shift * 2 * np.pi) * np.exp(1j * 2 * np.pi * t / fn.width)
return np.real(phi)
def _calc_rect(self, fn: FunctionSet, t: np.ndarray, shift: float) -> np.ndarray:
return np.where(np.abs(t - shift) < (fn.width / 2), fn.amplitude, 0)
def _calc_tri(self, fn: FunctionSet, t: np.ndarray, shift: float) -> np.ndarray:
saw = fn.amplitude * scipy.signal.sawtooth(2 * np.pi * ((t - shift) / fn.width))
return np.where(np.abs(t - shift) < (fn.width / 2), np.abs(saw), 0)
def _calc_gauss(self, fn: FunctionSet, t: np.ndarray, shift: float) -> np.ndarray:
return fn.amplitude * np.exp(-1 * np.power((t - shift), 2) / (2 * np.power(fn.width, 2)))
def calc_signal(self, fn: FunctionSet, t: np.ndarray, shift: float) -> np.ndarray:
if fn.function == FunctionType.Cos:
return fn.offset + self._calc_cos(fn, t, shift)
elif fn.function == FunctionType.Rect:
return fn.offset + self._calc_rect(fn, t, shift)
elif fn.function == FunctionType.Tri:
return fn.offset + self._calc_tri(fn, t, shift)
elif fn.function == FunctionType.Gauss:
return fn.offset + self._calc_gauss(fn, t, shift)
else:
raise KeyError
def calc_noise(self, t: np.ndarray) -> np.ndarray:
return np.random.normal(0, self.sigma, len(t))
def calc_signal_1(self, t: np.ndarray) -> np.ndarray:
return self.calc_signal(self.fn1, t, 0)
def calc_signal_2(self, t: np.ndarray) -> np.ndarray:
return self.calc_signal(self.fn2, t, self.tau) + self.calc_noise(t)
class Ch03CrossCorrelationFrame(BaseFrame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._config: ConfigCh03CrossCorrelation = default_store().get_config(ConfigCh03CrossCorrelation)
ctrl_frm = self._create_control()
ctrl_frm.pack(side=LEFT)
signal_frm = self._create_signal_canvas()
signal_frm.pack(expand=True, fill=BOTH)
def _create_control(self) -> ConfigControlFrame:
frm = self._config.make_config_widget(self)
frm.widgets_on_change(self._on_change)
return frm
def _on_change(self, _, __, ___):
default_store().save()
self.draw_signal()
def _create_signal_canvas(self) -> ttk.Widget:
frm = ttk.Frame(self)
self._signal_fig = Figure(figsize=(12, 6), dpi=100)
self._signal_canvas = FigureCanvasTkAgg(self._signal_fig, frm)
self._signal_canvas.get_tk_widget().pack(expand=True, fill=BOTH)
self.draw_signal()
return frm
def draw_signal(self):
LENGTH = 200
title = 'Auto Correlation' if self._config.sigma == 0 else 'Cross Correlation'
np.random.seed(self._config.random_seed)
self._signal_fig.clear()
ax_inp = self._signal_fig.add_subplot(2, 1, 1)
ax_cross = self._signal_fig.add_subplot(2, 1, 2)
ax_inp.set_xlim(-1.2, 1.2)
ax_inp.set_xlabel('time')
ax_inp.set_ylabel('value')
ax_inp.set_title('Signals')
ax_cross.set_xlim(-1.2, 1.2)
ax_cross.set_xlabel('time')
ax_cross.set_ylabel('value')
ax_cross.set_title(title)
t = np.linspace(-1.0, 1.0, LENGTH)
sig1 = self._config.calc_signal_1(t)
sig2 = self._config.calc_signal_2(t)
ax_inp.plot(t, sig1, label='Transmitted Signal', linestyle='solid', linewidth=1)
ax_inp.plot(t, sig2, label='Received Signal', linestyle='solid', linewidth=1)
ax_inp.legend()
sig_cross = np.correlate(sig1, sig2, mode='same')
ax_cross.plot(t, sig_cross, label=title, linestyle='solid', linewidth=1)
ax_cross.legend()
self._signal_canvas.draw()
class Ch03CrossCorrelationWindow(Window):
GROUP = Ch03Group
TITLE = 'Cross Correlation'
FRAME = Ch03CrossCorrelationFrame
if __name__ == '__main__':
Ch03CrossCorrelationWindow.main()