Source code for sionna.phy.signal.window

#
# SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0#
"""Blocks implementing windowing functions"""

import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from sionna.phy import Block
from sionna.phy.utils import expand_to_rank

[docs] class Window(Block): # pylint: disable=line-too-long r""" Abtract class defining a window function The window function is applied through element-wise multiplication. The window function is real-valued, i.e., has `tf.float` as `dtype`. The `dtype` of the output is the same as the `dtype` of the input ``x`` to which the window function is applied. The window function and the input must have the same precision. Parameters ---------- normalize: `bool`, (default `False`) If `True`, the window is normalized to have unit average power per coefficient. precision : `None` (default) | "single" | "double" Precision used for internal calculations and outputs. If set to `None`, :attr:`~sionna.phy.config.Config.precision` is used. Input ----- x : [..., N], `tf.complex` or `tf.float` The input to which the window function is applied. The window function is applied along the last dimension. The length of the last dimension ``N`` must be the same as the ``length`` of the window function. Output ------ y : [...,N], `tf.complex` or `tf.float` Output of the windowing operation """ def __init__(self, normalize=False, precision=None, **kwargs): super().__init__(precision=precision, **kwargs) assert isinstance(normalize, bool), "normalize must be bool" self._normalize = normalize @property def coefficients(self): """ [N], `tf.float` : Set/get raw window coefficients (before normalization) """ return self._coefficients @coefficients.setter def coefficients(self, v): self._coefficients = self._cast_or_check_precision(v) @property def length(self): """ `int` : Window length in number of samples """ return self.coefficients.shape[0] @property def normalize(self): """ `bool` : If `True`, the window is normalized to have unit average power per coefficient. """ return self._normalize
[docs] def show(self, samples_per_symbol, domain="time", scale="lin"): r"""Plot the window in time or frequency domain For the computation of the Fourier transform, a minimum DFT size of 1024 is assumed which is obtained through zero padding of the window coefficients in the time domain. Input ----- samples_per_symbol: `int` Number of samples per symbol, i.e., the oversampling factor domain: "time" (default) | "frequency" Desired domain scale: "lin" (default) | "db" y-scale of the magnitude in the frequency domain. Can be "lin" (i.e., linear) or "db" (, i.e., Decibel). """ assert domain in ["time", "frequency"], "Invalid domain" # Normalize if requested w = self.coefficients if self.normalize: energy = tf.reduce_mean(tf.square(w)) w = w / tf.cast(tf.sqrt(energy), w.dtype) # Sampling times n_min = -(self.length//2) n_max = n_min + self.length sampling_times = np.arange(n_min, n_max, dtype=np.float32) sampling_times /= samples_per_symbol # if domain=="time": plt.figure(figsize=(12,6)) plt.plot(sampling_times, np.real(w.numpy())) plt.title("Time domain") plt.grid() plt.xlabel(r"Normalized time $(t/T)$") plt.ylabel(r"$w(t)$") plt.xlim(sampling_times[0], sampling_times[-1]) else: assert scale in ["lin", "db"], "Invalid scale" fft_size = max(1024, w.shape[-1]) h = np.fft.fft(w.numpy(), fft_size) h = np.fft.fftshift(h) h = np.abs(h) plt.figure(figsize=(12,6)) if scale=="db": h = np.maximum(h, 1e-10) h = 10*np.log10(h) plt.ylabel(r"$|W(f)|$ (dB)") else: plt.ylabel(r"$|W(f)|$") f = np.linspace(-samples_per_symbol/2, samples_per_symbol/2, fft_size) plt.plot(f, h) plt.title("Frequency domain") plt.grid() plt.xlabel(r"Normalized frequency $(f/W)$") plt.xlim(f[0], f[-1])
def call(self, x): w = self.coefficients # Normalize if requested if self.normalize: energy = tf.reduce_mean(tf.square(w)) w = w / tf.cast(tf.sqrt(energy), w.dtype) # Expand to the same rank as the input for broadcasting w = expand_to_rank(w, tf.rank(x), 0) # Cast to correct dtype if necessary if x.dtype.is_complex: w = tf.complex(w, tf.zeros_like(w)) # Apply window y = w*x return y
[docs] class CustomWindow(Window): # pylint: disable=line-too-long r""" Block for defining custom window function The window function is applied through element-wise multiplication. Parameters ---------- coefficients: [N], `tf.float` Window coefficients normalize: `bool`, (default `False`) If `True`, the window is normalized to have unit average power per coefficient. precision : `None` (default) | "single" | "double" Precision used for internal calculations and outputs. If set to `None`, :attr:`~sionna.phy.config.Config.precision` is used. Input ----- x : [..., N], `tf.complex` or `tf.float` Input to which the window function is applied. The window function is applied along the last dimension. The length of the last dimension ``N`` must be the same as the ``length`` of the window function. Output ------ y : [...,N], `tf.complex` or `tf.float` Output of the windowing operation """ def __init__(self, coefficients, normalize=False, precision=None, **kwargs): super().__init__(normalize=normalize, precision=precision, **kwargs) self.coefficients = coefficients
[docs] class HannWindow(Window): # pylint: disable=line-too-long r""" Block for defining a Hann window function The window function is applied through element-wise multiplication. The Hann window is defined by .. math:: w_n = \sin^2 \left( \frac{\pi n}{N} \right), 0 \leq n \leq N-1 where :math:`N` is the window length. Parameters ---------- normalize: `bool`, (default `False`) If `True`, the window is normalized to have unit average power per coefficient. precision : `None` (default) | "single" | "double" Precision used for internal calculations and outputs. If set to `None`, :attr:`~sionna.phy.config.Config.precision` is used. Input ----- x : [..., N], `tf.complex` or `tf.float` The input to which the window function is applied. The window function is applied along the last dimension. The length of the last dimension ``N`` must be the same as the ``length`` of the window function. Output ------ y : [...,N], `tf.complex` or `tf.float` Output of the windowing operation """ def __init__(self, normalize=False, precision=None, **kwargs): super().__init__(normalize=normalize, precision=precision, **kwargs) def build(self, input_shape): length = input_shape[-1] n = np.arange(length) coefficients = np.square(np.sin(np.pi*n/length)) self.coefficients = coefficients
[docs] class HammingWindow(Window): # pylint: disable=line-too-long r""" Block for defining a Hamming window function The window function is applied through element-wise multiplication. The Hamming window is defined by .. math:: w_n = a_0 - (1-a_0) \cos \left( \frac{2 \pi n}{N} \right), 0 \leq n \leq N-1 where :math:`N` is the window length and :math:`a_0 = \frac{25}{46}`. Parameters ---------- normalize: `bool`, (default `False`) If `True`, the window is normalized to have unit average power per coefficient. precision : `None` (default) | "single" | "double" Precision used for internal calculations and outputs. If set to `None`, :attr:`~sionna.phy.config.Config.precision` is used. Input ----- x : [..., N], `tf.complex` or `tf.float` The input to which the window function is applied. The window function is applied along the last dimension. The length of the last dimension ``N`` must be the same as the ``length`` of the window function. Output ------ y : [...,N], `tf.complex` or `tf.float` Output of the windowing operation """ def __init__(self, normalize=False, precision=None, **kwargs): super().__init__(normalize=normalize, precision=precision, **kwargs) def build(self, input_shape): n = input_shape[-1] nn = np.arange(n) a0 = 25./46. a1 = 1. - a0 coefficients = a0 - a1*np.cos(2.*np.pi*nn/n) self.coefficients = coefficients
[docs] class BlackmanWindow(Window): # pylint: disable=line-too-long r""" Block for defining a Blackman window function The window function is applied through element-wise multiplication. The Blackman window is defined by .. math:: w_n = a_0 - a_1 \cos \left( \frac{2 \pi n}{N} \right) + a_2 \cos \left( \frac{4 \pi n}{N} \right), 0 \leq n \leq N-1 where :math:`N` is the window length, :math:`a_0 = \frac{7938}{18608}`, :math:`a_1 = \frac{9240}{18608}`, and :math:`a_2 = \frac{1430}{18608}`. Parameters ---------- normalize: `bool`, (default `False`) If `True`, the window is normalized to have unit average power per coefficient. precision : `None` (default) | "single" | "double" Precision used for internal calculations and outputs. If set to `None`, :attr:`~sionna.phy.config.Config.precision` is used. Input ----- x : [..., N], `tf.complex` or `tf.float` The input to which the window function is applied. The window function is applied along the last dimension. The length of the last dimension ``N`` must be the same as the ``length`` of the window function. Output ------ y : [...,N], `tf.complex` or `tf.float` Output of the windowing operation """ def __init__(self, normalize=False, precision=None, **kwargs): super().__init__(normalize=normalize, precision=precision, **kwargs) def build(self, input_shape): n = input_shape[-1] nn = np.arange(n) a0 = 7938./18608. a1 = 9240./18608. a2 = 1430./18608. coefficients = a0 - a1*np.cos(2.*np.pi*nn/n) + a2*np.cos(4.*np.pi*nn/n) self.coefficients = coefficients