Skip to content

Commit

Permalink
add option to generate board signal with no overlapping bands
Browse files Browse the repository at this point in the history
  • Loading branch information
zhiim committed Nov 13, 2024
1 parent b420b2d commit 9f8c6b2
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 20 deletions.
2 changes: 1 addition & 1 deletion doa_py/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,5 @@
file for details.
"""

__version__ = "0.2.2"
__version__ = "0.2.3"
__author__ = "Qian Xu"
2 changes: 0 additions & 2 deletions doa_py/algorithm/broadband.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ def cssm(
if fre_ref is None:
# Find the frequency point with the maximum power
fre_ref = fre_bins[np.argmax(np.abs(signal_fre_bins).sum(axis=0))]
print(fre_ref)

# Calculate the manifold matrix corresponding to the pre-estimated angles at
# the reference frequency point
Expand Down Expand Up @@ -214,7 +213,6 @@ def tops(

if fre_ref is None:
fre_ref = fre_bins[np.argmax(np.abs(signal_fre_bins).sum(axis=(0, 2)))]
print(fre_ref)

# index of reference frequency in FFT output
ref_index = int(fre_ref / (fs / fre_bins.size))
Expand Down
43 changes: 36 additions & 7 deletions doa_py/arrays.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import numpy as np

from .signals import BroadSignal
from .signals import BroadSignal, NarrowSignal, Signal

C = 3e8 # wave speed

Expand Down Expand Up @@ -88,11 +88,13 @@ def steering_vector(self, fre, angle_incidence, unit="deg"):

def received_signal(
self,
signal,
signal: Signal,
angle_incidence,
snr=None,
nsamples=100,
amp=None,
min_length_ratio=0.1,
no_overlap=False,
unit="deg",
):
"""Generate array received signal based on array signal model
Expand All @@ -110,6 +112,10 @@ def received_signal(
snr: Signal-to-noise ratio. If set to None, no noise will be added
nsamples (int): Number of snapshots, defaults to 100
amp: The amplitude of each signal, 1d numpy array
min_length_ratio (float): Minimum length ratio of the frequency
range in (f_max - f_min)
no_overlap (bool): If True, generate signals with non-overlapping
subbands
unit: The unit of the angle, `rad` represents radian,
`deg` represents degree. Defaults to 'deg'.
"""
Expand All @@ -118,16 +124,24 @@ def received_signal(

if isinstance(signal, BroadSignal):
received = self._gen_broadband(
signal, snr, nsamples, angle_incidence, amp
signal,
snr,
nsamples,
angle_incidence,
amp,
min_length_ratio,
no_overlap,
)
else:
if isinstance(signal, NarrowSignal):
received = self._gen_narrowband(
signal, snr, nsamples, angle_incidence, amp
)

return received

def _gen_narrowband(self, signal, snr, nsamples, angle_incidence, amp):
def _gen_narrowband(
self, signal: NarrowSignal, snr, nsamples, angle_incidence, amp
):
"""Generate narrowband received signal
`azimuth` and `elevation` are already in radians
Expand All @@ -150,7 +164,16 @@ def _gen_narrowband(self, signal, snr, nsamples, angle_incidence, amp):

return received

def _gen_broadband(self, signal, snr, nsamples, angle_incidence, amp):
def _gen_broadband(
self,
signal: BroadSignal,
snr,
nsamples,
angle_incidence,
amp,
min_length_ratio=0.1,
no_overlap=False,
):
"""Generate broadband received signal
`azimuth` and `elevation` are already in radians
Expand All @@ -162,7 +185,13 @@ def _gen_broadband(self, signal, snr, nsamples, angle_incidence, amp):

num_antennas = self._element_position.shape[0]

incidence_signal = signal.gen(n=num_signal, nsamples=nsamples, amp=amp)
incidence_signal = signal.gen(
n=num_signal,
nsamples=nsamples,
min_length_ratio=min_length_ratio,
no_overlap=no_overlap,
amp=amp,
)

# generate array signal in frequency domain
signal_fre_domain = np.fft.fft(incidence_signal, axis=1)
Expand Down
78 changes: 69 additions & 9 deletions doa_py/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,28 @@ def fs(self):
return self._fs

@abstractmethod
def gen(self, n, nsamples, amp=None):
def gen(
self, n, nsamples, min_length_ratio=0.1, no_overlap=False, amp=None
):
"""Generate sampled signals
Args:
n (int): Number of signals
nsamples (int): Number of snapshots
min_length_ratio (float): Minimum length ratio of the frequency
range in (f_max - f_min)
no_overlap (bool): If True, generate signals with non-overlapping
subbands
amp (np.array): Amplitude of the signals (1D array of size n), used
to define different amplitudes for different signals.
By default it will generate equal amplitude signal.
Returns:
signal (np.array): Sampled signals
"""
raise NotImplementedError

def _gen_fre_ranges(self, n, min_length_ratio=0.3):
def _gen_fre_bands(self, n, min_length_ratio=0.1):
"""Generate frequency ranges for each boardband signal
Args:
Expand All @@ -166,12 +184,41 @@ def _gen_fre_ranges(self, n, min_length_ratio=0.3):
(n, 2)
"""
min_length = (self._f_max - self._f_min) * min_length_ratio
ranges = np.zeros((n, 2))
bands = np.zeros((n, 2))
for i in range(n):
length = self._rng.uniform(min_length, self._f_max - self._f_min)
start = self._rng.uniform(self._f_min, self._f_max - length)
ranges[i] = [start, start + length]
return ranges
bands[i] = [start, start + length]
return bands

def _generate_non_overlapping_bands(self, n, min_length_ratio=0.1):
"""Generate n non-overlapping frequency bands within a specified range.
Args:
n (int): Number of non-overlapping bands to generate.
min_length_ratio (float): Minimum length of each band as a ratio of
the maximum possible length.
Returns:
np.ndarray: An array of shape (n, 2) where each row represents a
frequency band [start, end].
"""
max_length = (self._f_max - self._f_min) // n
min_length = max_length * min_length_ratio

bands = np.zeros((n, 2))

for i in range(n):
length = self._rng.uniform(min_length, max_length)
start = self._rng.uniform(
self._f_min + i * max_length,
self._f_min + (i + 1) * max_length - length,
)
new_band = [start, start + length]

bands[i] = new_band

return bands


class ChirpSignal(BroadSignal):
Expand All @@ -187,9 +234,15 @@ def __init__(self, f_min, f_max, fs, rng=None):
"""
super().__init__(f_min, f_max, fs, rng=rng)

def gen(self, n, nsamples, amp=None):
def gen(
self, n, nsamples, min_length_ratio=0.1, no_overlap=False, amp=None
):
amp = self._get_amp(amp, n)
fre_ranges = self._gen_fre_ranges(n)
if no_overlap:
fre_ranges = self._generate_non_overlapping_bands(
n, min_length_ratio
)
fre_ranges = self._gen_fre_bands(n, min_length_ratio)

t = np.arange(nsamples) * 1 / self._fs
f0 = fre_ranges[:, 0]
Expand Down Expand Up @@ -222,9 +275,16 @@ def __init__(self, f_min, f_max, fs, ncarriers=100, rng=None):
super().__init__(f_min, f_max, fs, rng=rng)
self._ncarriers = ncarriers

def gen(self, n, nsamples, amp=None):
def gen(
self, n, nsamples, min_length_ratio=0.1, no_overlap=False, amp=None
):
amp = self._get_amp(amp, n)
fre_ranges = self._gen_fre_ranges(n)
if no_overlap:
fre_ranges = self._generate_non_overlapping_bands(
n, min_length_ratio
)
else:
fre_ranges = self._gen_fre_bands(n, min_length_ratio)

# generate random carrier frequencies
fres = self._rng.uniform(
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "doa_py"
version = "0.2.2"
version = "0.2.3"
description = "DOA estimation algorithms implemented in Python"
readme = "README.md"
license = { text = "MIT" }
Expand Down

0 comments on commit 9f8c6b2

Please sign in to comment.