From 023c20016f56e1f74291aaf1b0eda2ff8ac73b26 Mon Sep 17 00:00:00 2001 From: Brendan Haines Date: Sun, 23 Mar 2025 01:44:12 -0600 Subject: [PATCH] I'm steering to the wrong place but have a somewhat working steering animation --- plots/09_beamforming.py | 96 +++++++++++++++++++++++++++++++++++------ plots/utils.py | 25 +++++++++++ 2 files changed, 109 insertions(+), 12 deletions(-) diff --git a/plots/09_beamforming.py b/plots/09_beamforming.py index a1ec528..c0f8c7d 100644 --- a/plots/09_beamforming.py +++ b/plots/09_beamforming.py @@ -1,6 +1,6 @@ # %% imports import numpy as np -from utils import AnimatedPlot, dir_assets +from utils import AnimatedPlot, db10, db20, dir_assets, wrap_phase # %% @@ -17,7 +17,6 @@ class PcolorPlot(AnimatedPlot): self.element_x = np.linspace(-x_range / 2, x_range / 2, elements) self.element_y = np.zeros(elements) self.angle = np.deg2rad(angle_deg) - self.element_phase = np.zeros(elements) self.element_phase = ( np.dot(np.array([self.element_x, self.element_y]).T, [np.sin(self.angle), np.cos(self.angle)]) * 2 * np.pi ) @@ -104,18 +103,91 @@ class PcolorMagPlot(PcolorPlot): ) +class Steer(AnimatedPlot): + def __init__(self, elements: int, spacing_lambda: float = 0.5, **kwargs): + super().__init__(**kwargs) + + self.ax.remove() + self.axs = [ + self.fig.add_subplot(3, 1, 1), + self.fig.add_subplot(3, 1, 2), + self.fig.add_subplot(3, 1, 3), + ] + + self.spacing_lambda = spacing_lambda + self.elements = elements + + def update(self, t: float): + for ax in self.axs: + ax.clear() + + # rewind, don't reset + if t > 0.5: + t = 1 - t + t *= 2 + + angle = np.deg2rad((t * 2 - 1) * 90) # steering angle + + def get_phase(position): + return wrap_phase(np.sin(angle) * position * 2 * np.pi, deg=False) + + ideal_position = self.spacing_lambda * np.linspace(-(self.elements - 1) / 2, (self.elements - 1) / 2, 1001) + ideal_phase = get_phase(ideal_position) + + element_position = self.spacing_lambda * np.linspace( + -(self.elements - 1) / 2, (self.elements - 1) / 2, self.elements + ) + element_phase = get_phase(element_position) + element_taper = np.ones(self.elements) + element_excitation = element_taper * np.exp(1j * element_phase) + + theta = np.linspace(-90, 90, 361) + fft_points = 128 + fft_period = 90 / self.spacing_lambda + theta_fft = np.linspace(0, fft_period, fft_points, endpoint=False) + ff_pattern = np.interp( + theta, + theta_fft, + np.fft.fft(np.concat([element_excitation, np.zeros(fft_points - self.elements)]), norm="backward") + / self.elements, + period=fft_period, + ) + + self.fig.suptitle(f"{np.rad2deg(angle):+5.1f}° Steer") + + rolloff = np.cos(np.deg2rad(theta)) + self.axs[0].set_ylabel("Farfield Magnitude [dB]") + self.axs[0].plot(theta, db20(rolloff), color="gray") + self.axs[0].plot(theta, db20(ff_pattern * rolloff)) + self.axs[0].set_xlim(-90, 90) + self.axs[0].set_ylim(-30, 5) + self.axs[0].set_xlabel("Theta [°]") + self.axs[0].grid(True) + self.axs[0].axvline(np.rad2deg(angle)) + + self.axs[1].set_ylabel("Excitation Phase") + self.axs[1].stem(element_position, np.rad2deg(element_phase)) + self.axs[1].plot(ideal_position, np.rad2deg(ideal_phase), linestyle="--") + self.axs[1].set_ylim(-200, 200) + + self.axs[2].set_ylabel("Excitation Magnitude") + self.axs[2].stem(element_position, element_taper) + # self.axs[2].set_xlabel("Element") + + # %% def generate(): - for elements in [ - 1, - 2, - 4, - 10, - ]: - PcolorPhasePlot(elements=elements, angle_deg=45).save(dir_assets / "beamforming" / f"phase_xz_{elements}el.gif") - PcolorMagPlot(elements=elements, angle_deg=45).save( - dir_assets / "beamforming" / f"magnitude_xz_{elements}el.gif" - ) + # for elements in [ + # 1, + # 2, + # 4, + # 10, + # ]: + # PcolorPhasePlot(elements=elements, angle_deg=45).save(dir_assets / "beamforming" / f"phase_xz_{elements}el.gif") + # PcolorMagPlot(elements=elements, angle_deg=45).save( + # dir_assets / "beamforming" / f"magnitude_xz_{elements}el.gif" + # ) + Steer(elements=16, spacing_lambda=0.5, frames=200).save(dir_assets / "beamforming" / "steering.gif", framerate=15) # %% diff --git a/plots/utils.py b/plots/utils.py index 5677ca0..95a3ba3 100644 --- a/plots/utils.py +++ b/plots/utils.py @@ -67,3 +67,28 @@ class AnimatedPlot(ABC): [0, 1] """ pass + + +def db20(v): + return 20 * np.log10(np.abs(v)) + + +def db10(v): + return 10 * np.log10(np.abs(v)) + + +def db2v(db): + return 10 ** (db / 20) + + +def db2w(db): + return 10 ** (db / 10) + + +def wrap_phase(phase, deg: bool): + if deg: + maxval = 180 + else: + maxval = np.pi + + return (phase + maxval) % (2 * maxval) - maxval