-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathexample_interactive.py
202 lines (164 loc) · 7.74 KB
/
example_interactive.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
"""
Interactive Example:
- Start the script and enhance your comprehension of CCMA
by dynamically adjusting the parameters in real-time.
- Observe that the uniform distribution performs poorly for both MA and CCMA,
highlighting the importance of choosing suitable kernels.
- Recognize the superior accuracy of the Pascal triangle kernel compared to Hanning.
However, be mindful that the Pascal kernel tends to be more accurate while sacrificing some smoothness.
Path Description
- The path consists of multiple straight lines, an arc, a sinus curve and a discontinuous jump.
- Consequently, the path provides C0-, C1- and C2-discontinuities.
"""
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, CheckButtons
import numpy as np
from ccma import CCMA
def close_points(points):
"""
The first point is concatenated to the end of the sequence to get a closed representation.
"""
return np.vstack([points, points[0]])
class InteractiveUpdater:
def __init__(self, sigma_init=.005, w_ma_init=4, w_cc_init=2, rho_init=0.05, distrib_init="hanning"):
self.w_ma = w_ma_init
self.w_cc = w_cc_init
self.sigma = sigma_init
self.rho = rho_init
self.distrib = distrib_init
self.x_max, self.x_min, self.y_max, self.y_min = None, None, None, None
self.points, self.noisy_points = None, None
self.update_data()
# Set up the initial figure and axis
fig, self.ax = plt.subplots(figsize=(11, 7))
# Create sliders
ax_w_ma = plt.axes([0.1, 0.25, 0.8, 0.03])
ax_w_cc = plt.axes([0.1, 0.21, 0.8, 0.03])
ax_sigma = plt.axes([0.1, 0.17, 0.8, 0.03])
ax_rho = plt.axes([0.1, 0.13, 0.8, 0.03])
ax_zoom = plt.axes([0.1, 0.09, 0.8, 0.03])
ax_x = plt.axes([0.1, 0.05, 0.8, 0.03])
ax_y = plt.axes([0.1, 0.01, 0.8, 0.03])
ax_checkboxes = plt.axes([0.05, 0.35, 0.15, 0.2])
# Set ranges and initial values for sliders
self.slider_w_ma = Slider(ax_w_ma, 'w_ma', 0, 20, valinit=self.w_ma, valstep=1)
self.slider_w_cc = Slider(ax_w_cc, 'w_cc', 0, 20, valinit=self.w_cc, valstep=1)
self.slider_sigma = Slider(ax_sigma, 'sigma', 0, 0.075, valinit=self.sigma)
self.slider_rho = Slider(ax_rho, 'rho', 0.05, 0.15, valinit=self.rho)
self.slider_zoom = Slider(ax_zoom, 'zoom', 1, 20, valinit=1)
self.slider_x = Slider(ax_x, 'shift_x', -1, 1, valinit=0)
self.slider_y = Slider(ax_y, 'shift_y', -1, 1, valinit=0)
# Attach the update function to the sliders
self.slider_w_ma.on_changed(self.update)
self.slider_w_cc.on_changed(self.update)
self.slider_sigma.on_changed(self.update)
self.slider_rho.on_changed(self.update)
self.slider_zoom.on_changed(self.update)
self.slider_x.on_changed(self.update)
self.slider_y.on_changed(self.update)
# Set checkboxes for kernels
self.checkbox_labels = ['hanning', 'pascal', 'uniform']
self.checkboxes = CheckButtons(
ax=ax_checkboxes,
labels=self.checkbox_labels,
actives=[ele for ele in [True, False, False]],
)
self.checkboxes.on_clicked(self.update)
# General settings
self.ax.grid(True)
self.ax.set_aspect('equal')
self.ax.legend()
self.ax.set_xlim([-2.25, 2.25])
self.ax.set_ylim([-1.25, 1.25])
self.ax.set_facecolor((0.95, 0.95, 0.95))
# Reduce the size of the plot to make space for sliders.
box = self.ax.get_position()
self.ax.set_position([box.x0 + 0.1, box.y0 + 0.2, box.width, box.height * 0.8])
self.update(0.0)
plt.show()
def update_data(self):
# Build path
points = []
# Straight line
start = np.array([-2, 1])
end = np.array([1, 1])
dist = np.linalg.norm(start - end)
points.append(np.linspace(start, end, int(dist / self.rho)))
# Circle
n_circle = int(np.pi / self.rho)
points.append(np.array([1 + np.sin(np.linspace(0, np.pi, n_circle)),
np.cos(-np.linspace(0, np.pi, n_circle))]).T[1:])
# Sinus wave
n_sinus = int(2 / self.rho)
points.append(np.array([np.linspace(1, -1, n_sinus),
-0.75 - 0.25 * np.cos(np.linspace(0, 2*np.pi, n_sinus))]).T[1:])
# Straight line 2
start = np.array([-1, -1])
end = np.array([-2.0, -1])
dist = np.linalg.norm(start - end)
points.append(np.linspace(start, end, int(dist / self.rho))[1:])
# Straight line 3
start = np.array([-2.0, -1])
end = np.array([-1.5, 0])
dist = np.linalg.norm(start - end)
points.append(np.linspace(start, end, int(dist / self.rho))[1:])
# Straight line 4
start = np.array([-2.0, 0])
end = np.array([-2.0, 1])
dist = np.linalg.norm(start - end)
points.append(np.linspace(start, end, int(dist / self.rho))[:-1])
# Concatenate points and add noise
self.points = np.row_stack(points)
noise = np.random.normal(0, self.sigma, self.points.shape)
self.noisy_points = self.points + noise
# Get boundaries of plot -- important for zoom and sliding option
self.x_max = max(self.points[:, 0]) + 0.25
self.x_min = min(self.points[:, 0]) - 0.25
self.y_max = max(self.points[:, 1]) + 0.25
self.y_min = min(self.points[:, 1]) - 0.25
def update(self, val):
# Handle checkbox clicks
self.checkboxes.eventson = False
if val in self.checkbox_labels:
for idx, option_label in enumerate(self.checkbox_labels):
if option_label == self.distrib:
self.checkboxes.set_active(idx)
self.distrib = val
self.checkboxes.eventson = True
# Reload data only if sigma or rho was changed.
if self.sigma != self.slider_sigma.val:
self.sigma = self.slider_sigma.val
self.update_data()
if self.rho != self.slider_rho.val:
self.rho = self.slider_rho.val
self.update_data()
self.w_ma = int(self.slider_w_ma.val)
self.w_cc = int(self.slider_w_cc.val)
# Create the CCMA-filter object and smooth
ccma = CCMA(self.w_ma, self.w_cc, distrib=self.distrib)
ccma_points = ccma.filter(self.noisy_points, mode="wrapping")
ma_points = ccma.filter(self.noisy_points, cc_mode=False, mode="wrapping")
# Visualize results
self.ax.clear()
self.ax.plot(*self.points.T, 'ro', markersize=4, alpha=0.5, label=f"original points")
self.ax.plot(*close_points(self.noisy_points).T, "k-o", linewidth=1, alpha=0.15, markersize=6, label="noisy points")
self.ax.plot(*close_points(ccma_points).T, linewidth=4, alpha=0.5, color="b", label=f"ccma-smoothed")
self.ax.plot(*close_points(ma_points).T, linewidth=4, alpha=0.5, color="green", label=f"ma-smoothed")
# General settings
self.ax.grid(True, color="white", linewidth=2)
self.ax.set_aspect('equal')
self.ax.legend()
# Handle zoom and shift
x_diff = (self.x_max - self.x_min) / self.slider_zoom.val
y_diff = (self.y_max - self.y_min) / self.slider_zoom.val
x_center = self.x_min + (self.x_max - self.x_min) * (self.slider_x.val + 1) / 2
y_center = self.y_min + (self.y_max - self.y_min) * (self.slider_y.val + 1) / 2
x_min_cur = x_center - x_diff/2
x_max_cur = x_center + x_diff/2
y_min_cur = y_center - y_diff/2
y_max_cur = y_center + y_diff/2
self.ax.set_xlim([x_min_cur, x_max_cur])
self.ax.set_ylim([y_min_cur, y_max_cur])
plt.draw()
# Starts CCMA interaction window
InteractiveUpdater()