examples/a2/algo/heatmap/heatmap_processor.py#
1# Copyright (c) Acconeer AB, 2024-2026
2# All rights reserved
3
4from __future__ import annotations
5
6import argparse
7
8import numpy as np
9
10from PySide6.QtGui import QTransform
11
12import pyqtgraph as pg
13
14import acconeer.exptool as et
15from acconeer.exptool import a2
16from acconeer.exptool.a2.algo.heatmap.conventional import (
17 HeatmapConfig,
18 HeatmapProcessor,
19 ProcessorResult,
20)
21
22
23SENSOR_CONFIG = a2.SensorConfig.single_subsweep(
24 # SensorConfig params.
25 sweeps_per_frame=1,
26 sweep_rate=None,
27 # Subsweep params.
28 start_point=60,
29 num_points=34,
30 step_length=12,
31 hwaas_max=100,
32 hwaas_min=4,
33 hwaas_exponent=4,
34 tx_antenna_mask=0b111,
35 rx_antenna_mask=0b1111,
36 profile=a2.Profile.PROFILE_1,
37)
38
39UPDATE_RATE = 20
40
41HEATMAP_CONFIG = HeatmapConfig(
42 num_az_angles=61,
43 az_angle_range=(-60, 60),
44 num_el_angles=1,
45 el_angle_range=(0, 0),
46 covariance_matrix_time_constant_s=0.20,
47 high_pass_filter_time_constant_s=5.0,
48 antenna_array=a2.AntennaArray.AP212,
49 heatmap_mode=HeatmapConfig.HeatmapMode.MOVEMENT, # or DISTANCE
50)
51
52DEGREE_SYMBOL = "\u00b0"
53BASE_STEP_LENGTH = 2.5e-3
54
55
56class PGUpdater:
57 """
58 Minimal version of plotting from exploration tool.
59 """
60
61 def __init__(self, session_config, heatmap_config) -> None:
62 self._config: a2.SessionConfig | None = session_config
63 self.heatmap_config = heatmap_config
64
65 def setup(self, win):
66 self.viridis_heatmap_image_item = pg.ImageItem()
67 self.viridis_heatmap_image_item.setLookupTable(et.utils.pg_mpl_cmap("viridis"))
68
69 self.viridis_heatmap_plot_item = win.addPlot(row=0, col=0, title="Heatmap Visualization")
70 self.viridis_heatmap_plot_item.getAxis("left").setLabel("Distance", units="m")
71 self.viridis_heatmap_plot_item.getAxis("bottom").setLabel("Az angle", units=DEGREE_SYMBOL)
72 self.viridis_heatmap_colorbar = self.viridis_heatmap_plot_item.addColorBar(
73 self.viridis_heatmap_image_item, colorMap="viridis"
74 )
75
76 self.viridis_heatmap_plot_item.clear()
77 self.viridis_heatmap_plot_item.addItem(self.viridis_heatmap_image_item)
78 self.viridis_heatmap_plot_smooth_max = et.utils.SmoothMax(
79 self._config.update_rate, tau_decay=0.1, tau_grow=0.1
80 )
81
82 start_point = self._config.sensor_config.subsweep.start_point
83 step_length = self._config.sensor_config.subsweep.step_length
84
85 self.viridis_heatmap_image_item.setTransform(
86 QTransform()
87 .translate(-self.heatmap_config.az_span / 2, start_point * BASE_STEP_LENGTH)
88 .scale(
89 self.heatmap_config.az_span / len(self.heatmap_config.az_angles),
90 step_length * BASE_STEP_LENGTH,
91 )
92 )
93
94 def update(self, processor_result: ProcessorResult) -> None:
95 """Plotting is achieved by repeated calls to update with new processorresults"""
96
97 if processor_result.heatmap is None:
98 return
99 heatmap = processor_result.heatmap[:, 0, :]
100 max_val = np.max(heatmap)
101
102 max_heat = self.viridis_heatmap_plot_smooth_max.update(max_val)
103 image = np.nan_to_num(heatmap)
104 self.viridis_heatmap_colorbar.setLevels((0, max_heat))
105 self.viridis_heatmap_image_item.updateImage(image, levels=(0, max_heat))
106
107
108def main():
109 parser = argparse.ArgumentParser()
110 parser.add_argument(
111 "--output-file",
112 default="data.h5",
113 help="path to h5 file to store the radar data",
114 )
115
116 args = parser.parse_args()
117
118 output_file = args.output_file
119
120 with (
121 a2.Client.open(
122 # Leaving this comment as is will automatically search for an
123 # EVK connected via USB. Otherwise, one of the following should be specified:
124 #
125 # ip_address="<ip address of host (like a RPi). E.g. 192.168.XXX.YYY>",
126 # or
127 # serial_port="<serial port of module. E.g. COM3 or /dev/ttyUSBx for Window/Linux>",
128 # or
129 # usb_device=True,
130 # or
131 # mock=True,
132 ) as client
133 ):
134 processor = HeatmapProcessor(SENSOR_CONFIG.subsweep, HEATMAP_CONFIG, UPDATE_RATE)
135
136 session_config = a2.SessionConfig([SENSOR_CONFIG], update_rate=UPDATE_RATE)
137 client.setup_session(session_config)
138
139 pg_updater = PGUpdater(session_config, HEATMAP_CONFIG)
140 pg_process = et.PGProcess(pg_updater)
141 pg_process.start()
142
143 # Here we create and attach a H5Recorder to the Client.
144 # The H5Recorder will sample all frames retrieved in the client.
145 # Once the "with"-block have been exited, the H5Recorder will
146 # wrap up and close the file.
147 with a2.H5Recorder(output_file, client, mode="w"):
148 client.start_session()
149
150 interrupt_handler = et.utils.ExampleInterruptHandler()
151 print("Press Ctrl-C to end session")
152
153 while not interrupt_handler.got_signal:
154 # Client will send its Results to the H5Recorder.
155 result = client.get_next(format=a2.Client.ResultFormat.SOLE)
156
157 # Post-process radar data and get the heatmap result
158 processor_result = processor.process(result)
159
160 # Make sure we have a heatmap to plot
161 if processor_result.heatmap is None:
162 continue
163
164 try:
165 # Plot the heatmap
166 pg_process.put_data(processor_result)
167 except et.PGProccessDiedException:
168 break
169
170 print("Disconnecting...")
171 pg_process.close()
172 client.stop_session()
173
174
175if __name__ == "__main__":
176 main()
View this example on GitHub: acconeer/acconeer-python-exploration