examples/a121/algo/surface_velocity/example_app.py

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