examples/a121/algo/surface_velocity/example_app.py#
1# Copyright (c) Acconeer AB, 2023-2026
2# All rights reserved
3
4from __future__ import annotations
5
6import numpy as np
7
8# Added here to force pyqtgraph to choose PySide
9import PySide6 # noqa: F401
10
11import pyqtgraph as pg
12
13import acconeer.exptool as et
14from acconeer.exptool import a121
15from acconeer.exptool.a121.algo.surface_velocity import ExampleApp, ExampleAppConfig
16
17
18def main():
19 args = a121.ExampleArgumentParser().parse_args()
20 et.utils.config_logging(args)
21
22 client = a121.Client.open(**a121.get_client_args(args))
23
24 example_app_config = ExampleAppConfig(
25 surface_distance=0.40,
26 sensor_angle=35,
27 num_points=4,
28 step_length=6,
29 hwaas=16,
30 sweeps_per_frame=128,
31 sweep_rate=2000,
32 time_series_length=1024,
33 )
34
35 example_app = ExampleApp(
36 client=client,
37 sensor_id=1,
38 example_app_config=example_app_config,
39 )
40 example_app.start()
41
42 pg_updater = PGUpdater(
43 example_app_config,
44 )
45
46 pg_process = et.PGProcess(pg_updater)
47 pg_process.start()
48
49 interrupt_handler = et.utils.ExampleInterruptHandler()
50
51 print(f"Measured distances (m): {np.around(example_app.processor.distances, 2)}")
52
53 while not interrupt_handler.got_signal:
54 example_app_result = example_app.get_next()
55 print(
56 f"Estimated velocity {np.around(example_app_result.velocity, 2)} m/s, "
57 f"at distance {np.around(example_app_result.distance_m, 2)} m"
58 )
59
60 try:
61 pg_process.put_data(example_app_result)
62 except et.PGProccessDiedException:
63 break
64
65 print("Disconnecting...")
66 pg_process.close()
67 client.close()
68
69
70class PGUpdater:
71 _VELOCITY_Y_SCALE_MARGIN_M = 0.25
72
73 def __init__(
74 self,
75 example_app_config: ExampleAppConfig,
76 ):
77 self.slow_zone = example_app_config.slow_zone
78 self.history_length_s = 10
79 if example_app_config.frame_rate is None:
80 estimated_frame_rate = (
81 example_app_config.sweep_rate / example_app_config.sweeps_per_frame
82 )
83 else:
84 estimated_frame_rate = example_app_config.frame_rate
85
86 self.history_length_n = int(np.around(self.history_length_s * estimated_frame_rate))
87
88 self.setup_is_done = False
89
90 def setup(self, win):
91 c0_dashed_pen = et.utils.pg_pen_cycler(0, width=2.5, style="--")
92
93 # Velocity plot
94
95 self.velocity_history_plot = self._create_plot(win, row=0, col=0)
96 self.velocity_history_plot.setTitle("Estimated velocity")
97 self.velocity_history_plot.setLabel(axis="left", text="Velocity", units="m/s")
98 self.velocity_history_plot.setLabel(axis="bottom", text="Time", units="s")
99 self.velocity_history_plot.addLegend(labelTextSize="10pt")
100 self.velocity_smooth_limits = et.utils.SmoothLimits()
101
102 self.velocity_curve = self.velocity_history_plot.plot(
103 pen=et.utils.pg_pen_cycler(0), name="Estimated velocity"
104 )
105
106 self.psd_html = (
107 '<div style="text-align: center">'
108 '<span style="color: #FFFFFF;font-size:13pt;">'
109 "{}</span></div>"
110 )
111
112 self.distance_text_item = pg.TextItem(
113 html=self.psd_html,
114 fill=pg.mkColor(0x1F, 0x77, 0xB4, 180),
115 anchor=(0.5, 0),
116 )
117
118 self.velocity_history_plot.addItem(self.distance_text_item)
119
120 self.velocity_history = np.zeros(self.history_length_n)
121
122 self.lower_std_history = np.zeros(self.history_length_n)
123 self.upper_std_history = np.zeros(self.history_length_n)
124
125 self.lower_std_curve = self.velocity_history_plot.plot()
126 self.upper_std_curve = self.velocity_history_plot.plot()
127
128 fbi = pg.FillBetweenItem(
129 self.lower_std_curve,
130 self.upper_std_curve,
131 brush=pg.mkBrush(f"{et.utils.color_cycler(0)}50"),
132 )
133
134 self.velocity_history_plot.addItem(fbi)
135
136 # PSD plot
137
138 self.psd_plot = self._create_plot(win, row=1, col=0)
139 self.psd_plot.setTitle("PSD<br>(colored area represents the slow zone)")
140 self.psd_plot.setLabel(axis="left", text="Power")
141 self.psd_plot.setLabel(axis="bottom", text="Velocity", units="m/s")
142 self.psd_plot.addLegend(labelTextSize="10pt")
143
144 self.psd_smooth_max = et.utils.SmoothMax(tau_grow=0.5, tau_decay=2.0)
145 self.psd_curve = self.psd_plot.plot(pen=et.utils.pg_pen_cycler(0), name="PSD")
146 self.psd_threshold = self.psd_plot.plot(pen=c0_dashed_pen, name="Threshold")
147
148 psd_slow_zone_color = et.utils.color_cycler(0)
149 psd_slow_zone_color = f"{psd_slow_zone_color}50"
150 psd_slow_zone_brush = pg.mkBrush(psd_slow_zone_color)
151
152 self.psd_slow_zone = pg.LinearRegionItem(brush=psd_slow_zone_brush, movable=False)
153 self.psd_plot.addItem(self.psd_slow_zone)
154
155 brush = et.utils.pg_brush_cycler(0)
156 self.psd_peak_plot_item = pg.PlotDataItem(
157 pen=None, symbol="o", symbolSize=8, symbolBrush=brush, symbolPen="k"
158 )
159 self.psd_plot.addItem(self.psd_peak_plot_item)
160
161 self.psd_plot.setLogMode(x=False, y=True)
162
163 def update(self, example_app_result) -> None:
164 processor_extra_result = example_app_result.processor_extra_result
165
166 lim = self.velocity_smooth_limits.update(example_app_result.velocity)
167
168 self.velocity_history_plot.setYRange(
169 lim[0] - self._VELOCITY_Y_SCALE_MARGIN_M, lim[1] + self._VELOCITY_Y_SCALE_MARGIN_M
170 )
171 self.velocity_history_plot.setXRange(-self.history_length_s, 0)
172
173 xs = np.linspace(-self.history_length_s, 0, self.history_length_n)
174
175 self.velocity_history = np.roll(self.velocity_history, -1)
176 self.velocity_history[-1] = example_app_result.velocity
177 self.velocity_curve.setData(xs, self.velocity_history)
178
179 velocity_html = self.psd_html.format(
180 f"Distance {np.around(example_app_result.distance_m, 2)} m"
181 )
182 self.distance_text_item.setHtml(velocity_html)
183 self.distance_text_item.setPos(
184 -self.history_length_s / 2, lim[1] + self._VELOCITY_Y_SCALE_MARGIN_M
185 )
186
187 self.lower_std_history = np.roll(self.lower_std_history, -1)
188 self.lower_std_history[-1] = (
189 example_app_result.velocity + 0.5 * processor_extra_result.peak_width
190 )
191 self.lower_std_curve.setData(xs, self.lower_std_history)
192
193 self.upper_std_history = np.roll(self.upper_std_history, -1)
194 self.upper_std_history[-1] = (
195 example_app_result.velocity - 0.5 * processor_extra_result.peak_width
196 )
197 self.upper_std_curve.setData(xs, self.upper_std_history)
198
199 lim = self.psd_smooth_max.update(processor_extra_result.psd)
200 self.psd_plot.setYRange(np.log(0.5), np.log(lim))
201 self.psd_plot.setXRange(
202 processor_extra_result.max_bin_vertical_vs[0],
203 processor_extra_result.max_bin_vertical_vs[-1],
204 )
205 self.psd_curve.setData(
206 processor_extra_result.vertical_velocities, processor_extra_result.psd
207 )
208 self.psd_threshold.setData(
209 processor_extra_result.vertical_velocities, processor_extra_result.psd_threshold
210 )
211 if processor_extra_result.peak_idx is not None:
212 self.psd_peak_plot_item.setData(
213 [processor_extra_result.vertical_velocities[processor_extra_result.peak_idx]],
214 [processor_extra_result.psd[processor_extra_result.peak_idx]],
215 )
216 else:
217 self.psd_peak_plot_item.clear()
218
219 middle_idx = int(np.around(processor_extra_result.vertical_velocities.shape[0] / 2))
220 self.psd_slow_zone.setRegion(
221 [
222 processor_extra_result.vertical_velocities[middle_idx - self.slow_zone],
223 processor_extra_result.vertical_velocities[middle_idx + self.slow_zone],
224 ]
225 )
226
227 @staticmethod
228 def _create_plot(parent: pg.GraphicsLayout, row: int, col: int) -> pg.PlotItem:
229 velocity_history_plot = parent.addPlot(row=row, col=col)
230 velocity_history_plot.setMenuEnabled(False)
231 velocity_history_plot.setMouseEnabled(x=False, y=False)
232 velocity_history_plot.hideButtons()
233 velocity_history_plot.showGrid(x=True, y=True, alpha=0.5)
234
235 return velocity_history_plot
236
237
238if __name__ == "__main__":
239 main()
View this example on GitHub: acconeer/acconeer-python-exploration