blob: 64334c78ad0eaab2aadcdf2c9fd1de3bc1a51ce8 [file] [log] [blame]
Philipp Lee9332bd2022-04-26 21:32:12 +02001# SPDX-License-Identifier: MPL-2.0
2# Copyright (c) 2022 Philipp Le <philipp@philipple.de>.
3# This Source Code Form is subject to the terms of the Mozilla Public
4# License, v. 2.0. If a copy of the MPL was not distributed with this
5# file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
7from __future__ import annotations
8
9from tkinter import ttk, LEFT, BOTH
10from pydantic import confloat, conint
11from dcs.config import default_store, ConfigObject, ui_create, ConfigControlFrame
12import numpy as np
13from dcs.frames.base import BaseFrame, Window
14from dcs.frames.groups import Ch03Group
15from typing import List
16from enum import Enum
17import scipy.signal
18
19import matplotlib
20matplotlib.use('TkAgg')
21from matplotlib.figure import Figure
22from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
23
24
25class FunctionType(str, Enum):
26 Cos = 'Cosine'
27 Rect = 'Rectangular'
28 Tri = 'Triangular'
29 Gauss = 'Gaussian'
30
31
32@ui_create
33class FunctionSet(ConfigObject):
34 function: FunctionType = FunctionType.Gauss
35 amplitude: confloat(ge=0.0, lt=10.0, multiple_of=0.01) = 5.0
36 width: confloat(ge=0.01, lt=1.0, multiple_of=0.01) = 0.5
37 offset: confloat(ge=-5.0, lt=5.0, multiple_of=0.01) = 0.0
38
39 def make_config_widget(self, parent: ttk.Widget) -> ConfigControlFrame:
40 frm = ConfigControlFrame(parent, borderwidth=1, relief='raised')
41
42 ttk.Label(frm, text='Function:').grid(row=0, column=0)
43 w = self.ui_create_function_dropdown(frm)
44 w.grid(row=0, column=1)
45 frm.add_widget(w)
46
47 ttk.Label(frm, text='Amplitude:').grid(row=1, column=0)
48 w = self.ui_create_amplitude(frm)
49 w.grid(row=1, column=1)
50 frm.add_widget(w)
51
52 ttk.Label(frm, text='Width:').grid(row=2, column=0)
53 w = self.ui_create_width(frm)
54 w.grid(row=2, column=1)
55 frm.add_widget(w)
56
57 ttk.Label(frm, text='Offset:').grid(row=3, column=0)
58 w = self.ui_create_offset(frm)
59 w.grid(row=3, column=1)
60 frm.add_widget(w)
61
62 return frm
63
64
65@ui_create
66class ConfigCh03CrossCorrelation(ConfigObject):
67 _KEY = 'ch03_cross'
68
69 fn1: FunctionSet = FunctionSet()
70 fn2: FunctionSet = FunctionSet()
71
72 tau: confloat(ge=-1.0, lt=1.0, multiple_of=0.01) = 0.2
73 sigma: confloat(ge=0, le=3.0, multiple_of=0.01) = 0.0
74
75 random_seed: conint(ge=0, lt=10000) = 1000
76
77 def make_config_widget(self, parent: ttk.Widget) -> ConfigControlFrame:
78 frm = ConfigControlFrame(parent, borderwidth=1, relief='raised')
79
80 ttk.Label(frm, text='Function 1').pack()
81 frm_fn1 = self.fn1.make_config_widget(frm)
82 frm_fn1.pack()
83 for w in frm_fn1.ctrl_widgets:
84 frm.add_widget(w)
85
86 ttk.Label(frm, text='Function 2').pack()
87 frm_fn2 = self.fn2.make_config_widget(frm)
88 frm_fn2.pack()
89 for w in frm_fn2.ctrl_widgets:
90 frm.add_widget(w)
91
92 ttk.Label(frm_fn2, text='Tau (Shift):').grid(row=4, column=0)
93 w = self.ui_create_tau(frm_fn2)
94 w.grid(row=4, column=1)
95 frm.add_widget(w)
96
97 ttk.Label(frm_fn2, text='Standard Deviation:').grid(row=5, column=0)
98 w = self.ui_create_sigma(frm_fn2)
99 w.grid(row=5, column=1)
100 frm.add_widget(w)
101
102 frm_general = ttk.Frame(frm, borderwidth=1, relief='raised')
103 frm_general.pack()
104
105 ttk.Label(frm_general, text='Random Seed:').grid(row=1, column=0)
106 w = self.ui_create_random_seed(frm_general)
107 w.grid(row=1, column=1)
108 frm.add_widget(w)
109
110 return frm
111
112 def _calc_cos(self, fn: FunctionSet, t: np.ndarray, shift: float) -> np.ndarray:
113 phi = fn.amplitude * np.exp(1j * shift * 2 * np.pi) * np.exp(1j * 2 * np.pi * t / fn.width)
114 return np.real(phi)
115
116 def _calc_rect(self, fn: FunctionSet, t: np.ndarray, shift: float) -> np.ndarray:
117 return np.where(np.abs(t - shift) < (fn.width / 2), fn.amplitude, 0)
118
119 def _calc_tri(self, fn: FunctionSet, t: np.ndarray, shift: float) -> np.ndarray:
120 saw = fn.amplitude * scipy.signal.sawtooth(2 * np.pi * ((t - shift) / fn.width))
121 return np.where(np.abs(t - shift) < (fn.width / 2), np.abs(saw), 0)
122
123 def _calc_gauss(self, fn: FunctionSet, t: np.ndarray, shift: float) -> np.ndarray:
124 return fn.amplitude * np.exp(-1 * np.power((t - shift), 2) / (2 * np.power(fn.width, 2)))
125
126 def calc_signal(self, fn: FunctionSet, t: np.ndarray, shift: float) -> np.ndarray:
127 if fn.function == FunctionType.Cos:
128 return fn.offset + self._calc_cos(fn, t, shift)
129 elif fn.function == FunctionType.Rect:
130 return fn.offset + self._calc_rect(fn, t, shift)
131 elif fn.function == FunctionType.Tri:
132 return fn.offset + self._calc_tri(fn, t, shift)
133 elif fn.function == FunctionType.Gauss:
134 return fn.offset + self._calc_gauss(fn, t, shift)
135 else:
136 raise KeyError
137
138 def calc_noise(self, t: np.ndarray) -> np.ndarray:
139 return np.random.normal(0, self.sigma, len(t))
140
141 def calc_signal_1(self, t: np.ndarray) -> np.ndarray:
142 return self.calc_signal(self.fn1, t, 0)
143
144 def calc_signal_2(self, t: np.ndarray) -> np.ndarray:
145 return self.calc_signal(self.fn2, t, self.tau) + self.calc_noise(t)
146
147
148class Ch03CrossCorrelationFrame(BaseFrame):
149 def __init__(self, *args, **kwargs):
150 super().__init__(*args, **kwargs)
151
152 self._config: ConfigCh03CrossCorrelation = default_store().get_config(ConfigCh03CrossCorrelation)
153
154 ctrl_frm = self._create_control()
155 ctrl_frm.pack(side=LEFT)
156
157 signal_frm = self._create_signal_canvas()
158 signal_frm.pack(expand=True, fill=BOTH)
159
160 def _create_control(self) -> ConfigControlFrame:
161 frm = self._config.make_config_widget(self)
162 frm.widgets_on_change(self._on_change)
163 return frm
164
165 def _on_change(self, _, __, ___):
166 default_store().save()
167 self.draw_signal()
168
169 def _create_signal_canvas(self) -> ttk.Widget:
170 frm = ttk.Frame(self)
171
172 self._signal_fig = Figure(figsize=(12, 6), dpi=100)
173
174 self._signal_canvas = FigureCanvasTkAgg(self._signal_fig, frm)
175 self._signal_canvas.get_tk_widget().pack(expand=True, fill=BOTH)
176
177 self.draw_signal()
178
179 return frm
180
181 def draw_signal(self):
182 LENGTH = 200
183
184 title = 'Auto Correlation' if self._config.sigma == 0 else 'Cross Correlation'
185
186 np.random.seed(self._config.random_seed)
187
188 self._signal_fig.clear()
189
190 ax_inp = self._signal_fig.add_subplot(2, 1, 1)
191 ax_cross = self._signal_fig.add_subplot(2, 1, 2)
192
193 ax_inp.set_xlim(-1.2, 1.2)
194 ax_inp.set_xlabel('time')
195 ax_inp.set_ylabel('value')
196 ax_inp.set_title('Signals')
197
198 ax_cross.set_xlim(-1.2, 1.2)
199 ax_cross.set_xlabel('time')
200 ax_cross.set_ylabel('value')
201 ax_cross.set_title(title)
202
203 t = np.linspace(-1.0, 1.0, LENGTH)
204
205 sig1 = self._config.calc_signal_1(t)
206 sig2 = self._config.calc_signal_2(t)
207 ax_inp.plot(t, sig1, label='Transmitted Signal', linestyle='solid', linewidth=1)
208 ax_inp.plot(t, sig2, label='Received Signal', linestyle='solid', linewidth=1)
209 ax_inp.legend()
210
211 sig_cross = np.correlate(sig1, sig2, mode='same')
212 ax_cross.plot(t, sig_cross, label=title, linestyle='solid', linewidth=1)
213 ax_cross.legend()
214
215 self._signal_canvas.draw()
216
217
218class Ch03CrossCorrelationWindow(Window):
219 GROUP = Ch03Group
220 TITLE = 'Cross Correlation'
221 FRAME = Ch03CrossCorrelationFrame
222
223
224if __name__ == '__main__':
225 Ch03CrossCorrelationWindow.main()
226