I'm steering to the wrong place but have a somewhat working steering animation

This commit is contained in:
Brendan Haines 2025-03-23 01:44:12 -06:00
parent f50496ed5d
commit 023c20016f
2 changed files with 109 additions and 12 deletions

View File

@ -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)
# %%

View File

@ -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