from typing import Callable, Optional, Type, TypeVar, Union
import numpy as np
from . import signal
from .axis import Axis
from .core import RelationProtocol
from .exc import ConvertingError
from .relation import Relation
from .help_types import ArrayLike, Number
from . import default_functions as default_functions
SP = TypeVar('SP', bound='Spectrum')
"""Instance of `Spectrum`"""
SSPR = Union['Spectrum', 'signal.Signal', Relation]
"""Instance of Spectrum or Signal or Relation"""
SPRN = Union['Spectrum', Relation, Number]
"""Instance of Spectrum or Signal or Relation or Number"""
SSPRN = Union['Spectrum', 'signal.Signal', Relation, Number]
"""Instance of Spectrum or Signal or Relation or Number"""
[docs]class Spectrum(Relation):
"""A class that describes the spectrum of a signal.
The `Spectrum` class derived from the :class:`signal_design.relation.Relation` class.
Each 'Spectrum' can be converted into a :class:`signal_design.signal.Signal` using method `get_spectrum`
To convert the `Spectrum` into a :class:`signal_design.signal.Signal`, the method defined in the
default_functions module is used. (:class:`signal_design.default_functions.spectrum2signal`).
Current method can be overridden by own.
When performing arithmetic operations on instances of the
:class:`signal_design.signal.Signal` class,
an instance of the `Spectrum` class will be extracted from
the :class:`signal_design.signal.Signal` instance, and arithmetic
operations will be performed
on this instance. An instance of :class:`signal_design.relation.Relation`
class will be converted into
the instance of `Spectrum` class.
"""
def __init__(
self,
frequency: Union[RelationProtocol, Axis, ArrayLike],
spectrum_amplitude: Optional[ArrayLike] = None,
signal: Optional['signal.Signal'] = None,
) -> None:
"""Initialization of instance of `Spectrum`.
Args:
frequency (Union[RelationProtocol, Axis, ArrayLike]):
An instance of :class:`signal_design.relation.Relation` class or inherited from it,
or `Axis` instance, or array_like object containing
numbers (real).
spectrum_amplitude (ArrayLike, optional):
None or array_like object containing numbers (real or complex).
Defaults to None.
"""
super().__init__(frequency, spectrum_amplitude)
self._signal = signal
@property
def frequency(self) -> Axis:
"""Frequency array axis.
Equal to property `x`.
Returns:
Axis: frequency array axis.
"""
return self.x
@property
def amplitude(self) -> np.ndarray:
"""Spectrum amplitude array.
Equal to property `y`.
Returns:
numpy.ndarray: spectrum amplitude array.
"""
return self.y
[docs] def get_signal(
self,
time: Optional[Union[Axis, int]] = None,
start_time: Optional[float] = None,
) -> 'signal.Signal':
"""Get signal from spectrum.
Compute the signal from the spectrum.
Args:
time (Axis, int, optional): Define time to calculate
signal. Defaults to None.
start_time (float, optional): If True then the signal will be
shifted to zero. Defaults to `False`.
Returns:
signal.Signal: instance of :class:`signal_design.signal.Signal` described this `Spectrum`.
"""
if self._signal is None or time:
signal_result = default_functions.spectrum2signal(
self, time, start_time
)
self._signal = signal.Signal(signal_result, spectrum=self)
return self._signal
[docs] def get_amplitude_spectrum(self: 'Spectrum') -> Relation:
"""Get amplitude spectrum.
Calculate the relationship between the frequency and the absolute
value of the spectrum amplitude.
Args:
self (SP): instance of Spectrum.
Returns:
Relation: new instance of Relation.
"""
return Relation(self.x.copy(), np.abs(self.y))
[docs] def get_phase_spectrum(self: 'Spectrum') -> Relation:
"""Get phase spectrum.
Calculate the relationship between frequency and phase of the spectrum.
Args:
self (SP): instance of Spectrum.
Returns:
Relation: new instance of Relation.
"""
return Relation(self.x.copy(), np.unwrap(np.angle(self.y)))
[docs] def get_reverse_filter(
self: SP,
percent: Union[float, int] = 5.0,
subtract_phase=True,
frequency_start: Optional[float] = None,
frequency_end: Optional[float] = None,
) -> SP:
"""Calculate filter of reversed signal.
Args:
self (SP): instance of Spectrum.
percent (Union[float, int], optional): level of added white noise
in percent. Defaults to 5.0.
subtract_phase (bool, optional): If True performs phase subtraction.
If False succeeds, add the phase. Defaults to True.
frequency_start (float, optional): The start frequency. Defaults to None.
frequency_end (float, optional): The end frequency. Defaults to None.
Returns:
SP: new instance of Spectrum.
"""
spectrum = self.select_data(frequency_start, frequency_end)
abs_spectrum = spectrum.get_amplitude_spectrum()
abs_spectrum = abs_spectrum + abs_spectrum.max() * percent / 100
reversed_abs_spectrum = 1 / abs_spectrum
if subtract_phase:
phase_spectrum = -1 * spectrum.get_phase_spectrum()
else:
phase_spectrum = 1 * spectrum.get_phase_spectrum()
result_spectrum = type(self).get_from_amplitude_phase(
reversed_abs_spectrum, phase_spectrum
)
return result_spectrum
[docs] def add_phase(self: SP, other: SSPR) -> SP:
"""Add phase to spectrum.
Args:
self (SP): instance of `Spectrum`
other (SSPR): Extracting the `Spectrum` from the object and adding
the phase `Spectrum` to the `Spectrum`.
Returns:
`SP`: new instance of `Spectrum`.
"""
sp_other = _input2spectrum(other)
return type(self).get_from_amplitude_phase(
self.get_amplitude_spectrum(),
self.get_phase_spectrum() + sp_other.get_phase_spectrum(),
)
[docs] def sub_phase(self: SP, other: SSPR) -> SP:
"""Subtract phase from spectrum.
Args:
self (SP): instance of `Spectrum`
other (SSPR): Extracting the `Spectrum` from the object and subtract
the phase `Spectrum` from the `Spectrum`.
Returns:
`SP`: new instance of `Spectrum`.
"""
sp_other = _input2spectrum(other)
return type(self).get_from_amplitude_phase(
self.get_amplitude_spectrum(),
self.get_phase_spectrum() - sp_other.get_phase_spectrum(),
)
[docs] @staticmethod
def get_frequency_axis_from_time(
time: Axis,
is_complex_data: bool = False
) -> Axis:
"""Get frequency axis from axis of time.
Method for getting a frequency axis using time axis.
If the data is complex, then the frequency axis will contain negative
part.
Args:
time (Axis): axis of time.
is_complex_data (bool): If the data is complex, then the frequency
axis will contain a negative part. If not, then vice versa.
Defaults to False.
Return:
Axis: frequency axis.
"""
return default_functions.get_frequency_axis(
time.size,
time.sample,
is_complex_data,
)
[docs] @classmethod
def get_from_amplitude_phase(
cls: Type[SP], amplitude_spectrum: Relation, phase_spectrum: Relation
) -> SP:
"""Calculate of the spectrum from the amplitude and phase spectrum.
The spectrum is calculated through the amplitude and phase spectrum
using the formula abs*exp(1j*phase).
Args:
cls (Type[SP]): Spectrum class.
amplitude_spectrum (Relation): Amplitude spectrum is instance of :class:`signal_design.relation.Relation`.
phase_spectrum (Relation): Phase spectrum is instance of :class:`signal_design.relation.Relation`.
Returns:
SP: new instance of `Spectrum`
"""
return cls(amplitude_spectrum * ((1.0j * phase_spectrum).exp()))
[docs] @classmethod
@input2spectrum
def convolve(cls: Type[SP], sp1: 'Spectrum', sp2: 'Spectrum') -> SP:
"""Convolution of two instances of :class:`signal_design.relation.Relation` and return new instance of
`Spectrum`. Instances of :class:`signal_design.signal.Signal` will be converted to `Spectrum`
Args:
cls (Type[SP]): `Spectrum` class.
r1 (SSPR): instance of :class:`signal_design.relation.Relation` or subclass of `Relation`
r2 (SSPR): instance of :class:`signal_design.relation.Relation` or subclass of `Relation`
Returns:
S: new instance of `Spectrum`.
"""
return super().convolve(sp1, sp2)
[docs] @classmethod
@input2spectrum
def correlate(cls: Type[SP], sp1: 'Spectrum', sp2: 'Spectrum') -> SP:
"""Correlation of two instances of :class:`signal_design.relation.Relation` and return new instance of
`Spectrum`. Instances of :class:`signal_design.signal.Signal` will be converted to `Spectrum`
Args:
cls (Type[Spectrum]): `Spectrum` class.
r1 (SSPR): instance of :class:`signal_design.relation.Relation` or subclass of `Relation`
r2 (SSPR): instance of :class:`signal_design.relation.Relation` or subclass of `Relation`
Returns:
S: new instance of `Spectrum`.
"""
return super().correlate(sp1, sp2)
[docs] @input2spectrum_operation
def __add__(self: SP, a: SPRN) -> SP:
return super().__add__(a)
[docs] @input2spectrum_operation
def __sub__(self: SP, a: SPRN) -> SP:
return super().__sub__(a)
[docs] @input2spectrum_operation
def __mul__(self: SP, a: SPRN) -> SP:
return super().__mul__(a)
[docs] @input2spectrum_operation
def __truediv__(self: SP, a: SPRN) -> SP:
return super().__truediv__(a)
[docs] @input2spectrum_operation
def __pow__(self: SP, a: SPRN) -> SP:
return super().__pow__(a)