├── .github └── workflows │ └── python-app.yml ├── .gitignore ├── .pyticksrc ├── .readthedocs.yml ├── README.md ├── doc ├── Makefile ├── _gallery │ ├── README.txt │ ├── data │ │ └── gabor.mat │ ├── noplot │ │ ├── noplot_4_1_2_sin_gauss_spwv.py │ │ ├── noplot_4_3_5_morlet_scalogram_spectrogram.py │ │ ├── noplot_4_3_5_page_margenau_hill.py │ │ ├── noplot_4_3_5_wv_smoothed_reassigned.py │ │ ├── noplot_5_3_renyi_information.py │ │ ├── noplot_5_5_scalogram_singularity.py │ │ └── noplot_5_5_scalogram_singularity_strength.py │ ├── plot_1_3_1_chirp.py │ ├── plot_1_3_1_chirp_spectrum.py │ ├── plot_1_3_1_chirp_wv.py │ ├── plot_1_3_1_noisy_chirp.py │ ├── plot_1_3_1_noisy_chirp_spectrum.py │ ├── plot_1_3_1_noisy_chirp_wv.py │ ├── plot_1_3_3_transient.py │ ├── plot_1_3_3_transient_spectrogram.py │ ├── plot_2_2_1_time_freq_localization.py │ ├── plot_2_2_2_heisenberg_gabor_inequality.py │ ├── plot_2_3_instantaneous_frequency.py │ ├── plot_2_4_group_delay.py │ ├── plot_2_4_grp_delay_inst_freq_comprison.py │ ├── plot_2_6_monocomp_nonstat_colored_gaussian_noise.py │ ├── plot_2_6_monocomp_nonstat_constfreq_expamp.py │ ├── plot_2_6_monocomp_nonstat_doppler.py │ ├── plot_2_6_monocomp_nonstat_linfreq_gaussamp.py │ ├── plot_2_7_multicomp_nonstat_instfreq_grpdlay.py │ ├── plot_2_7_multicomp_nonstat_stft.py │ ├── plot_3_1_2_spectrum.py │ ├── plot_3_1_2_stft.py │ ├── plot_3_1_4_atoms_hamming_stft.py │ ├── plot_3_1_4_atoms_short_hamming_stft.py │ ├── plot_3_1_4_frequency_resolution.py │ ├── plot_3_1_4_time_resolution.py │ ├── plot_3_3_2_biorthonormal_window.py │ ├── plot_3_3_2_biorthonormal_window_gabor.py │ ├── plot_3_3_2_biorthonormal_window_oversampled.py │ ├── plot_3_4_1_chirps_spectrogram_long_gaussian.py │ ├── plot_3_4_1_chirps_spectrogram_short_gaussian.py │ ├── plot_3_4_1_distant_components_long_gaussian.py │ ├── plot_3_4_1_distant_components_short_gaussian.py │ ├── plot_3_4_2_morlet_scalogram_complex_sinusoids.py │ ├── plot_3_4_2_morlet_scalogram_dirac_impulse.py │ ├── plot_4_1_1_doppler_wigner_ville.py │ ├── plot_4_1_1_pwv_atoms.py │ ├── plot_4_1_1_wv_analytic_signal.py │ ├── plot_4_1_1_wv_atoms.py │ ├── plot_4_1_1_wv_real_signal.py │ ├── plot_4_1_1_wv_wireframe.py │ ├── plot_4_1_2_sin_gauss_pwv.py │ ├── plot_4_1_2_sin_gauss_wv.py │ ├── plot_4_1_3_chirps_ambifunb.py │ ├── plot_4_1_3_chirps_wvd.py │ ├── plot_4_1_4_margenau_hill.py │ ├── plot_4_2_2_bertrand_hyperbolic_gd.py │ ├── plot_4_2_2_dflandrin_hyperbolic_gd.py │ ├── plot_4_2_2_morlet_scalogram_atoms.py │ ├── plot_4_2_2_unterberger_hyperbolic_gd.py │ ├── plot_4_2_3_impulse_wv.py │ ├── plot_4_2_3_wideband_ambiguity.py │ ├── plot_4_3_2_reassigned_spectrogram.py │ ├── plot_4_3_6_friedman_instfreq_density.py │ ├── plot_5_4_2_hough_noisy_chirp.py │ ├── plot_5_4_2_hough_simultaneous_chirp.py │ ├── plot_5_4_2_wv_noisy_chirp.py │ └── plot_5_4_2_wv_simultaneous_chirp.py ├── _templates │ ├── localtoc.html │ └── navigation.html ├── api.rst ├── auto_gallery.rst ├── conf.py ├── docstring_plots │ ├── generators │ │ ├── amplitude_modulated │ │ │ ├── amexpos_bilateral.py │ │ │ ├── amexpos_unilateral.py │ │ │ ├── amgauss1.py │ │ │ ├── amgauss2.py │ │ │ ├── amgauss3.py │ │ │ ├── amrect1.py │ │ │ └── amtriang1.py │ │ ├── analytic_signals │ │ │ ├── anaask.py │ │ │ ├── anabpsk.py │ │ │ ├── anafsk.py │ │ │ ├── anapulse.py │ │ │ ├── anaqpsk.py │ │ │ ├── anasing.py │ │ │ └── anastep.py │ │ ├── frequency_modulated │ │ │ ├── fmconst.py │ │ │ ├── fmhyp.py │ │ │ ├── fmlin.py │ │ │ ├── fmodany.py │ │ │ ├── fmpar.py │ │ │ ├── fmpower.py │ │ │ └── fmsin.py │ │ ├── misc │ │ │ ├── altes.py │ │ │ ├── atoms.py │ │ │ ├── doppler.py │ │ │ ├── klauder.py │ │ │ └── mexhat.py │ │ └── noise │ │ │ ├── dopnoise.py │ │ │ ├── noisecg.py │ │ │ └── noisecu.py │ └── processing │ │ ├── freq_domain │ │ ├── group_delay.py │ │ └── inst_freq.py │ │ ├── stft.py │ │ └── utils │ │ └── derive_window.py ├── index.rst ├── introduction.rst ├── make.bat ├── misc_plots │ ├── nonstationary_phase_plot.py │ ├── stationary_phase_plot.py │ ├── touchtone.py │ ├── touchtone_mean_convolve.py │ ├── uncertainty_example_plot.py │ └── uncertainty_stft.py ├── nonstationary_signals.rst ├── quickstart │ ├── intro_examples_1.rst │ └── intro_examples_2.rst └── requirements.txt ├── pyproject.toml ├── setup.cfg └── tftb ├── __init__.py ├── generators ├── __init__.py ├── amplitude_modulated.py ├── analytic_signals.py ├── frequency_modulated.py ├── misc.py ├── noise.py ├── tests │ ├── __init__.py │ ├── test_amplitude_modulations.py │ ├── test_analytic_signals.py │ ├── test_frequency_modulations.py │ ├── test_misc.py │ ├── test_noise.py │ └── test_utils.py └── utils.py ├── processing ├── __init__.py ├── affine.py ├── ambiguity.py ├── base.py ├── cohen.py ├── freq_domain.py ├── linear.py ├── plotifl.py ├── postprocessing.py ├── reassigned.py ├── tests │ ├── __init__.py │ ├── test_ambiguity.py │ ├── test_cohen.py │ ├── test_freq_domain.py │ ├── test_linear.py │ ├── test_postprocessing.py │ ├── test_time_domain.py │ └── test_utils.py ├── time_domain.py └── utils.py ├── tests ├── __init__.py ├── test_base.py └── test_utils.py └── utils.py /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Python application 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | build: 17 | 18 | runs-on: ubuntu-latest 19 | strategy: 20 | matrix: 21 | python-version: ["3.9", "3.10", "3.11"] 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Set up Python 3 26 | uses: actions/setup-python@v3 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install poetry==2.0.0 32 | poetry add flake8 pytest coverage 33 | poetry install --no-interaction --no-root 34 | - name: Lint with flake8 35 | run: | 36 | poetry run flake8 . 37 | - name: unittests 38 | run: | 39 | poetry run coverage run -m pytest 40 | poetry run coverage report -m 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | doc/_build/ 55 | doc/modules/generated/ 56 | 57 | # PyBuilder 58 | target/ 59 | 60 | .idea* 61 | .ipynb_checkpoints/ 62 | *.swp 63 | 64 | # Skip sphinx-gallery build directory 65 | doc/auto_examples 66 | 67 | .pytest_cache 68 | .vscode 69 | doc/backreferences 70 | doc/apiref/ 71 | 72 | # Avoid poetry lockfiles 73 | poetry.lock 74 | -------------------------------------------------------------------------------- /.pyticksrc: -------------------------------------------------------------------------------- 1 | [main] 2 | default_remote: upstream 3 | cache_location: /Users/jaidevd/.pyticks_cache.json 4 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: doc/conf.py 11 | 12 | # Build documentation with MkDocs 13 | #mkdocs: 14 | # configuration: mkdocs.yml 15 | 16 | # Optionally build your docs in additional formats such as PDF and ePub 17 | formats: all 18 | 19 | python: 20 | version: 3.7 21 | install: 22 | - requirements: doc/requirements.txt 23 | - method: pip 24 | path: . 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tftb 2 | 3 | tftb (Time-frequency toolbox) is a Python module for time-frequency analysis and visualization 4 | build with SciPy and matplotlib. 5 | 6 | The tftb project began as a Python implementation of the TFTB toolbox developed by 7 | François Auger, Olivier Lemoine, Paulo Gonçalvès and Patrick Flandrin. While this project and the 8 | MATLAB implementation (henceforth referred to as TFTB) are similar in the core algorithms and the 9 | basic code organization, the very nature of the Python programming language has motivated a very 10 | different approach in architecture of PyTFTB (differences between the two packages have been 11 | discussed in detail [here](https://tftb.readthedocs.io/en/latest/introduction.html#comparison-of-tftb-and-pytftb)). 12 | 13 | 14 | ## Installation 15 | 16 | tftb requires: 17 | - Python (>= 3.5) 18 | - NumPy 19 | - SciPy 20 | - Matplotlib 21 | 22 | Install tftb with pip as follows: 23 | ```bash 24 | $ pip install tftb 25 | ``` 26 | -------------------------------------------------------------------------------- /doc/_gallery/README.txt: -------------------------------------------------------------------------------- 1 | .. _general_examples: 2 | 3 | General examples 4 | ---------------- 5 | 6 | General-purpose and introductory examples for PyTFTB 7 | -------------------------------------------------------------------------------- /doc/_gallery/data/gabor.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-signal/tftb/18c784d9a8539436165aff6f6ca5f100b116ee8b/doc/_gallery/data/gabor.mat -------------------------------------------------------------------------------- /doc/_gallery/noplot/noplot_4_1_2_sin_gauss_spwv.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Example from section 4.1.2 of the tutorials. 11 | 12 | Figure 4.10 from the tutorial. 13 | """ 14 | 15 | from tftb.generators import fmconst, amgauss 16 | from tftb.processing import smoothed_pseudo_wigner_ville 17 | import numpy as np 18 | import matplotlib.pyplot as plt 19 | from mpl_toolkits.axes_grid1 import make_axes_locatable 20 | from scipy.signal import kaiser, hamming 21 | 22 | twindow = kaiser(13, 3 * np.pi) 23 | fwindow = kaiser(33, 3 * np.pi) 24 | twindow = hamming(13) 25 | fwindow = hamming(33) 26 | 27 | sig = fmconst(128, 0.15)[0] + amgauss(128) * fmconst(128, 0.4)[0] 28 | tfr = smoothed_pseudo_wigner_ville(sig, twindow=twindow, fwindow=fwindow, 29 | freq_bins=128) 30 | threshold = np.abs(tfr) * 0.05 31 | tfr[np.abs(tfr) <= threshold] = 0.0 32 | 33 | fig, axImage = plt.subplots() 34 | axImage.contour(np.abs(tfr), extent=[0, 128, 0, 0.5], levels=list(range(5))) 35 | axImage.grid(True) 36 | axImage.set_title("Wigner Ville distribution") 37 | axImage.set_ylabel('Frequency') 38 | axImage.set_xlabel('Time') 39 | 40 | divider = make_axes_locatable(axImage) 41 | axTime = divider.append_axes("top", 1.2, pad=0.5) 42 | axFreq = divider.append_axes("left", 1.2, pad=0.5) 43 | axTime.plot(np.real(sig)) 44 | axTime.set_xlim(0, 128) 45 | axTime.set_ylabel('Real part') 46 | axTime.set_title('Signal in time') 47 | axTime.grid(True) 48 | spectrum = abs(np.fft.fftshift(np.fft.fft(sig)) ** 2)[64:] 49 | axFreq.plot(spectrum, np.arange(spectrum.shape[0])) 50 | axFreq.set_ylabel('Spectrum') 51 | axFreq.grid(True) 52 | plt.show() 53 | -------------------------------------------------------------------------------- /doc/_gallery/noplot/noplot_4_3_5_morlet_scalogram_spectrogram.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Comparison of the Spectrogram and the morlet scalogram with their reassinged 11 | counterparts. 12 | 13 | Figure 4.36 from the tutorial 14 | """ 15 | 16 | import numpy as np 17 | import matplotlib.pyplot as plt 18 | from tftb.generators import fmsin, fmlin, fmconst 19 | from tftb.processing.cohen import Spectrogram 20 | from tftb.processing.reassigned import spectrogram as re_spectrogram 21 | from tftb.processing.reassigned import morlet_scalogram as re_morlet_scalogram 22 | from tftb.processing import ideal_tfr 23 | 24 | sig1, if1 = fmsin(60, 0.16, 0.35, 50, 1, 0.35, 1) 25 | sig2, if2 = fmlin(60, 0.3, 0.1) 26 | sig3, if3 = fmconst(60, 0.4) 27 | 28 | sig = np.hstack((sig1, np.zeros((8,)), sig2 + sig3)) 29 | iflaw = np.zeros((2, 128)) 30 | iflaw[0, :] = np.hstack((if1, np.nan * np.ones((8,)), if2)) 31 | iflaw[1, :] = np.hstack((np.nan * np.ones((68,)), if3)) 32 | 33 | tfr, t, f = ideal_tfr(iflaw) 34 | plt.figure(figsize=(10, 8)) 35 | plt.subplot(221) 36 | plt.contour(t, f, tfr, 1) 37 | plt.grid(True) 38 | plt.gca().set_xticklabels([]) 39 | plt.title("Ideal instantaneous frequencies") 40 | plt.ylabel('Normalized Frequencies') 41 | 42 | tfr, _, _ = Spectrogram(sig).run() 43 | threshold = np.amax(np.abs(tfr)) * 0.05 44 | tfr[np.abs(tfr) <= threshold] = 0.0 45 | plt.subplot(222) 46 | plt.imshow(np.abs(tfr)[:64, :], extent=[0, 128, 0, 0.5], aspect='auto', origin='lower') 47 | plt.grid(True) 48 | plt.gca().set_xticklabels([]) 49 | plt.gca().set_yticklabels([]) 50 | plt.title("Spectrogram") 51 | 52 | _, tfr, _ = re_spectrogram(sig) 53 | tfr = tfr[:64, :] 54 | threshold = np.amax(np.abs(tfr) ** 2) * 0.05 55 | tfr[np.abs(tfr) ** 2 <= threshold] = 0.0 56 | plt.subplot(223) 57 | plt.imshow(np.abs(tfr) ** 2, extent=[0, 128, 0, 0.5], aspect='auto', origin='lower') 58 | plt.grid(True) 59 | plt.title("Reassigned spectrogram") 60 | plt.xlabel('Time') 61 | plt.ylabel('Normalized Frequencies') 62 | 63 | _, rtfr, _ = re_morlet_scalogram(sig) 64 | rtfr = rtfr[:64, :] 65 | threshold = np.amax(np.abs(rtfr) ** 2) * 0.05 66 | rtfr[np.abs(rtfr) ** 2 <= threshold] = 0.0 67 | plt.subplot(224) 68 | plt.imshow(np.abs(rtfr) ** 2, extent=[0, 128, 0, 0.5], aspect='auto', origin='lower') 69 | plt.gca().set_yticklabels([]) 70 | plt.grid(True) 71 | plt.title("Reassigned Morlet Scalogram") 72 | plt.xlabel('Time') 73 | 74 | plt.show() 75 | -------------------------------------------------------------------------------- /doc/_gallery/noplot/noplot_4_3_5_page_margenau_hill.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Comparison of the pseudo Page and the pseudo Margenau-Hill distributions with 11 | their reassinged counterparts. 12 | 13 | Figure 4.37 from the tutorial. 14 | """ 15 | 16 | import numpy as np 17 | import matplotlib.pyplot as plt 18 | from tftb.generators import fmsin, fmlin, fmconst 19 | from tftb.processing.cohen import PseudoPageRepresentation, PseudoMargenauHillDistribution 20 | from tftb.processing.reassigned import pseudo_page as re_pseudo_page 21 | from tftb.processing.reassigned import pseudo_margenau_hill as re_pseudo_margenau_hill 22 | 23 | sig1, if1 = fmsin(60, 0.16, 0.35, 50, 1, 0.35, 1) 24 | sig2, if2 = fmlin(60, 0.3, 0.1) 25 | sig3, if3 = fmconst(60, 0.4) 26 | 27 | sig = np.hstack((sig1, np.zeros((8,)), sig2 + sig3)) 28 | iflaw = np.zeros((2, 128)) 29 | iflaw[0, :] = np.hstack((if1, np.nan * np.ones((8,)), if2)) 30 | iflaw[1, :] = np.hstack((np.nan * np.ones((68,)), if3)) 31 | 32 | tfr, t, f = PseudoPageRepresentation(sig).run() 33 | tfr = np.abs(tfr) ** 2 34 | threshold = np.amax(tfr) * 0.05 35 | tfr[tfr <= threshold] = 0.0 36 | 37 | plt.figure(figsize=(10, 8)) 38 | plt.subplot(221) 39 | plt.imshow(tfr[:64, :], extent=[0, 128, 0, 0.5], aspect='auto', origin='lower') 40 | plt.gca().set_xticklabels([]) 41 | plt.grid(True) 42 | plt.title("Pseudo Page distribution") 43 | plt.ylabel('Normalized Frequencies') 44 | 45 | _, tfr, _ = re_pseudo_page(sig) 46 | tfr = np.abs(tfr) ** 2 47 | threshold = np.amax(tfr) * 0.05 48 | tfr[tfr <= threshold] = 0.0 49 | plt.subplot(222) 50 | plt.imshow(tfr[:64, :], extent=[0, 128, 0, 0.5], aspect='auto', origin='lower') 51 | plt.grid(True) 52 | plt.title("Reassigned Pseudo Page distribution") 53 | plt.gca().set_xticklabels([]) 54 | plt.gca().set_yticklabels([]) 55 | 56 | tfr, _, _ = PseudoMargenauHillDistribution(sig).run() 57 | tfr = np.abs(tfr) ** 2 58 | threshold = np.amax(tfr) * 0.05 59 | tfr[tfr <= threshold] = 0.0 60 | plt.subplot(223) 61 | plt.imshow(tfr[:64, :], extent=[0, 128, 0, 0.5], aspect='auto', origin='lower') 62 | plt.grid(True) 63 | plt.title("Pseudo Margenau Hill distribution") 64 | plt.xlabel('Time') 65 | plt.ylabel('Normalized Frequencies') 66 | 67 | _, rtfr, _ = re_pseudo_margenau_hill(sig) 68 | tfr = np.abs(tfr) ** 2 69 | threshold = np.amax(tfr) * 0.05 70 | tfr[tfr <= threshold] = 0.0 71 | plt.subplot(224) 72 | plt.imshow(tfr[:64, :], extent=[0, 128, 0, 0.5], aspect='auto', origin='lower') 73 | plt.gca().set_yticklabels([]) 74 | plt.grid(True) 75 | plt.title("Reassigned Pseudo Margenau Hill distribution") 76 | plt.xlabel('Time') 77 | 78 | plt.show() 79 | -------------------------------------------------------------------------------- /doc/_gallery/noplot/noplot_4_3_5_wv_smoothed_reassigned.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Comparison of the Wigner Ville distribution with its smoothed and reassinged 11 | counterparts. 12 | 13 | Figure 4.35 from the tutorial. 14 | """ 15 | 16 | import numpy as np 17 | import matplotlib.pyplot as plt 18 | from tftb.generators import fmsin, fmlin, fmconst 19 | from tftb.processing import (ideal_tfr, WignerVilleDistribution, 20 | smoothed_pseudo_wigner_ville, reassigned_smoothed_pseudo_wigner_ville) 21 | 22 | sig1, if1 = fmsin(60, 0.16, 0.35, 50, 1, 0.35, 1) 23 | sig2, if2 = fmlin(60, 0.3, 0.1) 24 | sig3, if3 = fmconst(60, 0.4) 25 | 26 | sig = np.hstack((sig1, np.zeros((8,)), sig2 + sig3)) 27 | iflaw = np.zeros((2, 128)) 28 | iflaw[0, :] = np.hstack((if1, np.nan * np.ones((8,)), if2)) 29 | iflaw[1, :] = np.hstack((np.nan * np.ones((68,)), if3)) 30 | 31 | tfr, t, f = ideal_tfr(iflaw) 32 | 33 | plt.figure(figsize=(10, 8)) 34 | plt.subplot(221) 35 | plt.contour(t, f, tfr, 1) 36 | plt.gca().set_xticklabels([]) 37 | plt.grid(True) 38 | plt.title("Ideal instantaneous frequencies") 39 | plt.ylabel('Normalized Frequencies') 40 | 41 | tfr = WignerVilleDistribution(sig).run()[0] 42 | threshold = np.amax(np.abs(tfr) ** 2) * 0.05 43 | tfr[np.abs(tfr) ** 2 <= threshold] = 0.0 44 | plt.subplot(222) 45 | plt.imshow(np.abs(tfr) ** 2, extent=[0, 128, 0, 0.5], aspect='auto', origin='lower') 46 | plt.grid(True) 47 | plt.title("WV distro") 48 | plt.gca().set_xticklabels([]) 49 | plt.gca().set_yticklabels([]) 50 | 51 | tfr = smoothed_pseudo_wigner_ville(sig) 52 | threshold = np.amax(np.abs(tfr) ** 2) * 0.05 53 | tfr[np.abs(tfr) ** 2 <= threshold] = 0.0 54 | plt.subplot(223) 55 | plt.imshow(np.abs(tfr) ** 2, extent=[0, 128, 0, 0.5], aspect='auto', origin='lower') 56 | plt.grid(True) 57 | plt.title("Smoothed Pseudo WV distro") 58 | plt.xlabel('Time') 59 | plt.ylabel('Normalized Frequencies') 60 | 61 | _, rtfr, _ = reassigned_smoothed_pseudo_wigner_ville(sig) 62 | threshold = np.amax(np.abs(rtfr) ** 2) * 0.05 63 | rtfr[np.abs(rtfr) ** 2 <= threshold] = 0.0 64 | plt.subplot(224) 65 | plt.imshow(np.abs(rtfr) ** 2, extent=[0, 128, 0, 0.5], aspect='auto', origin='lower') 66 | plt.grid(True) 67 | plt.title("Reassigned Smoothed Pseudo WV distro") 68 | plt.xlabel('Time') 69 | plt.gca().set_yticklabels([]) 70 | 71 | plt.show() 72 | -------------------------------------------------------------------------------- /doc/_gallery/noplot/noplot_5_3_renyi_information.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Examples showing Renyi information measurement. 11 | """ 12 | 13 | import numpy as np 14 | from scipy.io import loadmat 15 | from tftb.generators import atoms 16 | from tftb.processing import renyi_information 17 | from tftb.processing.cohen import WignerVilleDistribution 18 | 19 | sig = atoms(128, np.array([[64, 0.25, 20, 1]])) 20 | tfr, t, f = WignerVilleDistribution(sig).run() 21 | ideal = loadmat("/tmp/foo.mat") 22 | print(renyi_information(tfr, t, f)) # -0.2075 23 | 24 | sig = atoms(128, np.array([[32, 0.25, 20, 1], [96, 0.25, 20, 1]])) 25 | tfr, t, f = WignerVilleDistribution(sig).run() 26 | print(renyi_information(tfr, t, f)) # 0.77 27 | 28 | sig = atoms(128, np.array([[32, 0.15, 20, 1], [96, 0.15, 20, 1], 29 | [32, 0.35, 20, 1], [96, 0.35, 20, 1]])) 30 | tfr, t, f = WignerVilleDistribution(sig).run() 31 | print(renyi_information(tfr, t, f)) # 1.8029 32 | -------------------------------------------------------------------------------- /doc/_gallery/noplot/noplot_5_5_scalogram_singularity.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Morlet Scalograms of Lipschitz singularities. 11 | 12 | Figure 5.7 from the tutorial 13 | """ 14 | 15 | from tftb.processing import scalogram 16 | from tftb.generators import anasing 17 | import numpy as np 18 | from mpl_toolkits.axes_grid1 import make_axes_locatable 19 | import matplotlib.pyplot as plt 20 | 21 | 22 | sig = anasing(64) 23 | 24 | tfr, t, f, _ = scalogram(sig, waveparams=4, fmin=0.01, fmax=0.5, n_voices=256) 25 | 26 | t, f = np.meshgrid(t, f) 27 | 28 | fig, axContour = plt.subplots() 29 | axContour.contour(t, f, tfr, 10) 30 | axContour.grid(True) 31 | axContour.set_title("Morlet Scalogram of Lipschitz singularity") 32 | axContour.set_ylabel('Frequency') 33 | axContour.set_xlabel('Time') 34 | 35 | divider = make_axes_locatable(axContour) 36 | axTime = divider.append_axes("top", 1.2, pad=0.5) 37 | axFreq = divider.append_axes("left", 1.2, pad=0.5) 38 | axTime.plot(np.real(sig)) 39 | axTime.set_xlim(0, 64) 40 | axTime.set_ylabel('Real part') 41 | axTime.set_title('Signal in time') 42 | axTime.grid(True) 43 | axFreq.plot((abs(np.fft.fftshift(np.fft.fft(sig))) ** 2)[::-1], 44 | np.arange(sig.shape[0])) 45 | axFreq.set_ylabel('Spectrum') 46 | axFreq.grid(True) 47 | plt.show() 48 | plt.show() 49 | -------------------------------------------------------------------------------- /doc/_gallery/noplot/noplot_5_5_scalogram_singularity_strength.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | =========================================== 11 | Morlet Scalogram of a Lipschitz Singularity 12 | =========================================== 13 | 14 | The time localization of the Lipschitz function can be seen at smaller scales. 15 | 16 | Figure 5.8 from the tutorial. 17 | """ 18 | 19 | from tftb.processing import scalogram 20 | from tftb.generators import anasing 21 | import numpy as np 22 | from mpl_toolkits.axes_grid1 import make_axes_locatable 23 | import matplotlib.pyplot as plt 24 | 25 | 26 | sig = anasing(64, 32, -0.5) 27 | 28 | tfr, t, f, _ = scalogram(sig, waveparams=4, fmin=0.01, fmax=0.5, n_voices=256) 29 | 30 | t, f = np.meshgrid(t, f) 31 | 32 | fig, axContour = plt.subplots() 33 | axContour.contour(t, f, tfr, 10) 34 | axContour.grid(True) 35 | axContour.set_title("Morlet Scalogram of Lipschitz singularity") 36 | axContour.set_ylabel('Frequency') 37 | axContour.set_xlabel('Time') 38 | 39 | divider = make_axes_locatable(axContour) 40 | axTime = divider.append_axes("top", 1.2, pad=0.5) 41 | axFreq = divider.append_axes("left", 1.2, pad=0.5) 42 | axTime.plot(np.real(sig)) 43 | axTime.set_xlim(0, 64) 44 | axTime.set_ylabel('Real part') 45 | axTime.set_title('Signal in time') 46 | axTime.grid(True) 47 | axFreq.plot((abs(np.fft.fftshift(np.fft.fft(sig))) ** 2)[::-1], 48 | np.arange(sig.shape[0])) 49 | axFreq.set_ylabel('Spectrum') 50 | axFreq.grid(True) 51 | plt.show() 52 | plt.show() 53 | -------------------------------------------------------------------------------- /doc/_gallery/plot_1_3_1_chirp.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | =========================== 11 | Linear Frequency Modulation 12 | =========================== 13 | 14 | This example shows how PyTFTB is used to generate a signal with linear 15 | frequency modulation. Such a signal is also called a `chirp 16 | `_. 17 | 18 | Figure 1.1 from the tutorial. 19 | """ 20 | 21 | from tftb.generators import fmlin 22 | import matplotlib.pyplot as plt 23 | import numpy as np 24 | 25 | # Generate a chirp signal 26 | 27 | n_points = 128 28 | fmin, fmax = 0.0, 0.5 29 | 30 | signal, _ = fmlin(n_points, fmin, fmax) 31 | plt.plot(np.real(signal)) 32 | plt.xlim(0, n_points) 33 | plt.title('Linear Frequency Modulation') 34 | plt.ylabel('Real Part') 35 | plt.xlabel('Time') 36 | plt.grid() 37 | plt.show() 38 | -------------------------------------------------------------------------------- /doc/_gallery/plot_1_3_1_chirp_spectrum.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ================================== 11 | Energy Spectral Density of a Chirp 12 | ================================== 13 | 14 | Construct a chirp and plot its `energy spectral density 15 | `_. 16 | 17 | Figure 1.2 from the tutorial. 18 | """ 19 | 20 | from tftb.generators import fmlin 21 | import matplotlib.pyplot as plt 22 | import numpy as np 23 | 24 | n_points = 128 25 | fmin, fmax = 0.0, 0.5 26 | signal, _ = fmlin(n_points, fmin, fmax) 27 | 28 | # Plot the energy spectrum of the chirp 29 | 30 | dsp1 = np.fft.fftshift(np.abs(np.fft.fft(signal)) ** 2) 31 | plt.plot(np.arange(-64, 64, dtype=float) / 128.0, dsp1) 32 | plt.xlim(-0.5, 0.5) 33 | plt.title('Spectrum') 34 | plt.ylabel('Squared modulus') 35 | plt.xlabel('Normalized Frequency') 36 | plt.grid() 37 | plt.show() 38 | -------------------------------------------------------------------------------- /doc/_gallery/plot_1_3_1_chirp_wv.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ==================================== 11 | Wigner-Ville Distribution of a Chirp 12 | ==================================== 13 | 14 | Construct a chirp signal and visualize its `Wigner-Ville distribution 15 | `_. 16 | 17 | Figure 1.3 from the tutorial. 18 | """ 19 | 20 | from tftb.generators import fmlin 21 | from tftb.processing.cohen import WignerVilleDistribution 22 | 23 | n_points = 128 24 | fmin, fmax = 0.0, 0.5 25 | signal, _ = fmlin(n_points, fmin, fmax) 26 | 27 | # Wigner-Ville distribution of the chirp. 28 | 29 | wvd = WignerVilleDistribution(signal) 30 | wvd.run() 31 | wvd.plot(kind='contour', extent=[0, n_points, fmin, fmax]) 32 | -------------------------------------------------------------------------------- /doc/_gallery/plot_1_3_1_noisy_chirp.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ======================== 11 | Generating a Noisy Chirp 12 | ======================== 13 | 14 | This example shows how to generate a chirp signal, with some analytical 15 | gaussian noise, and the usage of the :ref:`sigmerge` function to combine them. 16 | 17 | Figure 1.4 from the tutorial. 18 | """ 19 | 20 | from tftb.generators import fmlin, sigmerge, noisecg 21 | import matplotlib.pyplot as plt 22 | import numpy as np 23 | 24 | # Generate a chirp signal 25 | 26 | n_points = 128 27 | fmin, fmax = 0.0, 0.5 28 | 29 | signal, _ = fmlin(n_points, fmin, fmax) 30 | 31 | # Noisy chirp 32 | 33 | noisy_signal = sigmerge(signal, noisecg(128), 0) 34 | plt.plot(np.real(noisy_signal)) 35 | plt.xlim(0, 128) 36 | plt.title('Noisy chirp') 37 | plt.ylabel('Real Part') 38 | plt.xlabel('Time') 39 | plt.grid() 40 | plt.show() 41 | -------------------------------------------------------------------------------- /doc/_gallery/plot_1_3_1_noisy_chirp_spectrum.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ================================ 11 | Energy Spectrum of a Noisy Chirp 12 | ================================ 13 | 14 | Generate a noisy chirp and plot its energy spectrum. 15 | 16 | Figure 1.5 from the tutorial. 17 | """ 18 | 19 | from tftb.generators import fmlin, sigmerge, noisecg 20 | import matplotlib.pyplot as plt 21 | import numpy as np 22 | 23 | # Generate a chirp signal 24 | 25 | n_points = 128 26 | fmin, fmax = 0.0, 0.5 27 | 28 | signal, _ = fmlin(n_points, fmin, fmax) 29 | 30 | # Noisy chirp 31 | 32 | noisy_signal = sigmerge(signal, noisecg(128), 0) 33 | 34 | # Enery spectrum of the noisy chirp. 35 | 36 | dsp1 = np.fft.fftshift(np.abs(np.fft.fft(noisy_signal)) ** 2) 37 | plt.plot(np.arange(-64, 64, dtype=float) / 128.0, dsp1) 38 | plt.xlim(-0.5, 0.5) 39 | plt.title('Spectrum of Noisy Chirp') 40 | plt.ylabel('Squared modulus') 41 | plt.xlabel('Normalized Frequency') 42 | plt.grid() 43 | plt.show() 44 | -------------------------------------------------------------------------------- /doc/_gallery/plot_1_3_1_noisy_chirp_wv.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ========================================== 11 | Wigner-Ville Distribution of a Noisy Chirp 12 | ========================================== 13 | 14 | Generate a noisy chirp and visualize its Wigner-Ville spectrum. 15 | 16 | Figure 1.6 from the tutorial. 17 | """ 18 | 19 | from tftb.generators import fmlin, sigmerge, noisecg 20 | from tftb.processing.cohen import WignerVilleDistribution 21 | 22 | # Generate a chirp signal 23 | 24 | n_points = 128 25 | fmin, fmax = 0.0, 0.5 26 | 27 | signal, _ = fmlin(n_points, fmin, fmax) 28 | 29 | # Noisy chirp 30 | 31 | noisy_signal = sigmerge(signal, noisecg(128), 0) 32 | 33 | 34 | # Wigner-Ville spectrum of noisy chirp. 35 | 36 | wvd = WignerVilleDistribution(noisy_signal) 37 | wvd.run() 38 | wvd.plot(kind='contour') 39 | -------------------------------------------------------------------------------- /doc/_gallery/plot_1_3_3_transient.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ==================================== 11 | Spectrum of a Noisy Transient Signal 12 | ==================================== 13 | 14 | This example shows how to generate a noisy transient signal with the following 15 | characteristics: 16 | 17 | * One-sided exponential amplitude modulation (See :ref:`amexpos`) 18 | * Constant frequency modulation (See :ref:`fmconst`) 19 | * -5 dB complex gaussian noise (See :ref:`noisecg` and :ref:`sigmerge`) 20 | 21 | And how to plot its energy spectrum. 22 | 23 | Figure 1.10 of the tutorial. 24 | """ 25 | 26 | 27 | import numpy as np 28 | import matplotlib.pyplot as plt 29 | from tftb.generators import amexpos, fmconst, sigmerge, noisecg 30 | 31 | # Generate a noisy transient signal. 32 | transsig = amexpos(64, kind='unilateral') * fmconst(64)[0] 33 | signal = np.hstack((np.zeros((100,)), transsig, np.zeros((92,)))) 34 | signal = sigmerge(signal, noisecg(256), -5) 35 | fig, ax = plt.subplots(2, 1) 36 | ax1, ax2 = ax 37 | ax1.plot(np.real(signal)) 38 | ax1.grid() 39 | ax1.set_title('Noisy Transient Signal') 40 | ax1.set_xlabel('Time') 41 | ax1.set_xlim((0, 256)) 42 | ax1.set_ylim((np.real(signal).max(), np.real(signal.min()))) 43 | 44 | # Energy spectrum of the signal 45 | dsp = np.fft.fftshift(np.abs(np.fft.fft(signal)) ** 2) 46 | ax2.plot(np.arange(-128, 128, dtype=float) / 256, dsp) 47 | ax2.set_title('Energy spectrum of noisy transient signal') 48 | ax2.set_xlabel('Normalized frequency') 49 | ax2.grid() 50 | ax2.set_xlim(-0.5, 0.5) 51 | 52 | plt.subplots_adjust(hspace=0.5) 53 | 54 | plt.show() 55 | -------------------------------------------------------------------------------- /doc/_gallery/plot_1_3_3_transient_spectrogram.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ======================================= 11 | Spectrogram of a Noisy Transient Signal 12 | ======================================= 13 | 14 | This example demonstrates the simple use of a Spectrogram to localize a signal 15 | in time and frequency. The transient signal appears at the normalized frequency 16 | 0.25 and between time points 125 and 160. 17 | 18 | Figure 1.11 from the tutorial. 19 | """ 20 | 21 | 22 | import numpy as np 23 | from scipy.signal import hamming 24 | from tftb.generators import amexpos, fmconst, sigmerge, noisecg 25 | from tftb.processing.cohen import Spectrogram 26 | 27 | # Generate a noisy transient signal. 28 | transsig = amexpos(64, kind='unilateral') * fmconst(64)[0] 29 | signal = np.hstack((np.zeros((100,)), transsig, np.zeros((92,)))) 30 | signal = sigmerge(signal, noisecg(256), -5) 31 | 32 | fwindow = hamming(65) 33 | spec = Spectrogram(signal, n_fbins=128, fwindow=fwindow) 34 | spec.run() 35 | spec.plot(kind="contour", threshold=0.1, show_tf=False) 36 | -------------------------------------------------------------------------------- /doc/_gallery/plot_2_2_1_time_freq_localization.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | =============================================== 11 | Time and Frequency Localization Characteristics 12 | =============================================== 13 | 14 | Generate a signal that has localized characteristics in both time and frequency 15 | and compute the following estimates: 16 | 17 | * time center 18 | * time duration 19 | * frequency center 20 | * frequency spreading 21 | 22 | Example 2.1 from the tutorial. 23 | """ 24 | 25 | from tftb.generators import fmlin, amgauss 26 | from tftb.processing import loctime, locfreq 27 | import numpy as np 28 | import matplotlib.pyplot as plt 29 | 30 | # generate signal 31 | signal = fmlin(256)[0] * amgauss(256) 32 | plt.subplot(211), plt.plot(np.real(signal)) 33 | plt.xlim(0, 256) 34 | plt.xlabel('Time') 35 | plt.ylabel('Real part') 36 | plt.title('Signal') 37 | plt.grid() 38 | fsig = np.fft.fftshift(np.abs(np.fft.fft(signal)) ** 2) 39 | plt.subplot(212), plt.plot(np.linspace(-0.5, 0.5, 256), fsig) 40 | plt.xlabel('Normalized frequency') 41 | plt.ylabel('Squared modulus') 42 | plt.title('Spectrum') 43 | plt.grid() 44 | plt.subplots_adjust(hspace=0.5) 45 | plt.show() 46 | 47 | 48 | tm, T = loctime(signal) 49 | print("Time Center: {}".format(tm)) 50 | print("Time Duration: {}".format(T)) 51 | num, B = locfreq(signal) 52 | print("Frequency Center: {}".format(num)) 53 | print("Frequency Spreading: {}".format(B)) 54 | -------------------------------------------------------------------------------- /doc/_gallery/plot_2_2_2_heisenberg_gabor_inequality.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ========================== 11 | Heisenbeg-Gabor Inequality 12 | ========================== 13 | 14 | This example demonstrates the `Heisenberg-Gabor inequality 15 | `_. 16 | 17 | Simply put, the inequality states that the time-bandwidth product of a signal 18 | is lower bound by some constant (in this case normalized to unity). This means 19 | that a signal cannot have arbitrarily high precision in time and frequency 20 | simultaneously. 21 | 22 | Figure 2.2 from the tutorial. 23 | 24 | """ 25 | 26 | from tftb.generators import amgauss 27 | from tftb.processing import loctime, locfreq 28 | import numpy as np 29 | import matplotlib.pyplot as plt 30 | 31 | # generate signal 32 | signal = amgauss(256) 33 | plt.subplot(211), plt.plot(np.real(signal)) 34 | plt.xlim(0, 256) 35 | plt.xlabel('Time') 36 | plt.ylabel('Real part') 37 | plt.title('Signal') 38 | plt.grid() 39 | fsig = np.fft.fftshift(np.abs(np.fft.fft(signal)) ** 2) 40 | plt.subplot(212), plt.plot(np.linspace(-0.5, 0.5, 256), fsig) 41 | plt.xlabel('Normalized frequency') 42 | plt.ylabel('Squared modulus') 43 | plt.title('Spectrum') 44 | plt.grid() 45 | plt.subplots_adjust(hspace=0.5) 46 | plt.show() 47 | 48 | tm, T = loctime(signal) 49 | print("Time Center: {}".format(tm)) 50 | print("Time Duration: {}".format(T)) 51 | fm, B = locfreq(signal) 52 | print("Frequency Center: {}".format(fm)) 53 | print("Frequency Spreading: {}".format(B)) 54 | print("Time-bandwidth product: {}".format(T * B)) 55 | -------------------------------------------------------------------------------- /doc/_gallery/plot_2_3_instantaneous_frequency.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ============================================== 11 | Estimate the Instantaneous Freuqncy of a Chirp 12 | ============================================== 13 | 14 | Construct a chirp and estimate its `instantaneous 15 | frequency `_. 16 | 17 | Figure 2.3 from the tutorial. 18 | """ 19 | 20 | from tftb.generators import fmlin 21 | from tftb.processing import plotifl, inst_freq 22 | # TODO: There doesn't have to be something called `plotifl`. Put this into a 23 | # separate visualization module. 24 | import numpy as np 25 | 26 | 27 | signal, _ = fmlin(256) 28 | time_samples = np.arange(3, 257) 29 | ifr = inst_freq(signal)[0] 30 | plotifl(time_samples, ifr) 31 | -------------------------------------------------------------------------------- /doc/_gallery/plot_2_4_group_delay.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ================================= 11 | Group Delay Estimation of a Chirp 12 | ================================= 13 | 14 | Constuct a chirp and estimates its `group delay 15 | `_. 16 | 17 | Figure 2.4 from the tutorial. 18 | """ 19 | 20 | from tftb.generators import fmlin 21 | from tftb.processing import group_delay 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | 25 | signal, _ = fmlin(256) 26 | fnorm = np.linspace(0, .5, 10) 27 | gd = group_delay(signal, fnorm) 28 | plt.plot(gd, fnorm) 29 | plt.grid(True) 30 | plt.xlim(0, 256) 31 | plt.xlabel('Time') 32 | plt.ylabel('Normalized Frequency') 33 | plt.title('Group Delay Estimation') 34 | plt.show() 35 | -------------------------------------------------------------------------------- /doc/_gallery/plot_2_4_grp_delay_inst_freq_comprison.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ===================================================== 11 | Comparison of Instantaneous Frequency and Group Delay 12 | ===================================================== 13 | 14 | Instantaneous frequency and group delay are very closely related. The former is 15 | the frequency of a signal at a given instant, and the latter is the time delay 16 | of frequency components. As this example shows, they coincide with each other 17 | for a given signal when the time bandwidth product of the signal is 18 | sufficiently high. 19 | 20 | Figure 2.5 from the tutorial. 21 | """ 22 | 23 | import numpy as np 24 | import matplotlib.pyplot as plt 25 | from tftb.generators import amgauss, fmlin 26 | from tftb.processing import loctime, locfreq, inst_freq, group_delay 27 | 28 | time_instants = np.arange(2, 256) 29 | sig1 = amgauss(256, 128, 90) * fmlin(256)[0] 30 | tm, T1 = loctime(sig1) 31 | fm, B1 = locfreq(sig1) 32 | ifr1 = inst_freq(sig1, time_instants)[0] 33 | f1 = np.linspace(0, 0.5 - 1.0 / 256, 256) 34 | gd1 = group_delay(sig1, f1) 35 | 36 | plt.subplot(211) 37 | plt.plot(time_instants, ifr1, '*', label='inst_freq') 38 | plt.plot(gd1, f1, '-', label='group delay') 39 | plt.xlim(0, 256) 40 | plt.grid(True) 41 | plt.legend() 42 | plt.title("Time-Bandwidth product: {0}".format(T1 * B1)) 43 | plt.xlabel('Time') 44 | plt.ylabel('Normalized Frequency') 45 | 46 | 47 | sig2 = amgauss(256, 128, 30) * fmlin(256, 0.2, 0.4)[0] 48 | tm, T2 = loctime(sig2) 49 | fm, B2 = locfreq(sig2) 50 | ifr2 = inst_freq(sig2, time_instants)[0] 51 | f2 = np.linspace(0.02, 0.4, 256) 52 | gd2 = group_delay(sig2, f2) 53 | 54 | 55 | plt.subplot(212) 56 | plt.plot(time_instants, ifr2, '*', label='inst_freq') 57 | plt.plot(gd2, f2, '-', label='group delay') 58 | plt.ylim(0.2, 0.4) 59 | plt.xlim(0, 256) 60 | plt.grid(True) 61 | plt.legend() 62 | plt.title("Time-Bandwidth product: {0}".format(T2 * B2)) 63 | plt.xlabel('Time') 64 | plt.ylabel('Normalized Frequency') 65 | 66 | plt.subplots_adjust(hspace=0.5) 67 | 68 | plt.show() 69 | -------------------------------------------------------------------------------- /doc/_gallery/plot_2_6_monocomp_nonstat_colored_gaussian_noise.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ========================= 11 | Noisy Monocomponent Chirp 12 | ========================= 13 | 14 | This example demonstrates the construction of a monocomponent signal with 15 | linear frequency modulation and colored Gaussian noise. 16 | 17 | Figure 2.9 from the tutorial. 18 | """ 19 | 20 | from tftb.generators import fmlin, amgauss, noisecg, sigmerge 21 | from numpy import real 22 | import matplotlib.pyplot as plt 23 | 24 | fm, _ = fmlin(256) 25 | am = amgauss(256) 26 | signal = fm * am 27 | 28 | noise = noisecg(256, .8) 29 | sign = sigmerge(signal, noise, -10) 30 | 31 | plt.plot(real(sign)) 32 | plt.xlabel('Time') 33 | plt.ylabel('Real part') 34 | plt.title('Gaussian transient signal embedded in -10 dB colored Gaussian noise') 35 | plt.xlim(0, 256) 36 | plt.grid() 37 | plt.show() 38 | -------------------------------------------------------------------------------- /doc/_gallery/plot_2_6_monocomp_nonstat_constfreq_expamp.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ==================================================================================================================== 11 | Monocomponent Nonstationary Signal with Constant Frequency Modulation and One-Sided Exponential Amplitude Modulation 12 | ==================================================================================================================== 13 | 14 | Generate a monocomponent nonstationary signal with constant frequency 15 | modulation and one-sided exponential amplitude modulation. 16 | 17 | Figure 2.7 from the tutorial. 18 | """ 19 | 20 | from tftb.generators import fmconst, amexpos 21 | import matplotlib.pyplot as plt 22 | from numpy import real 23 | 24 | fm, _ = fmconst(256, 0.2) 25 | am = amexpos(256, 100, kind='unilateral') 26 | signal = am * fm 27 | 28 | plt.plot(real(signal)) 29 | plt.xlabel('Time') 30 | plt.ylabel('Real part') 31 | plt.title('Constant Frequency, One-sided Exponential Amplitude') 32 | plt.xlim(0, 256) 33 | plt.grid() 34 | plt.show() 35 | -------------------------------------------------------------------------------- /doc/_gallery/plot_2_6_monocomp_nonstat_doppler.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ============== 11 | Doppler Signal 12 | ============== 13 | 14 | Generate a Doppler Signal. 15 | 16 | Figure 2.8 from the tutorial. 17 | """ 18 | 19 | from tftb.generators import doppler 20 | from numpy import real 21 | import matplotlib.pyplot as plt 22 | 23 | fm, am, _ = doppler(256.0, 200.0, 4000.0 / 60.0, 10.0, 50.0) 24 | signal = am * fm 25 | 26 | plt.plot(real(signal)) 27 | plt.xlabel('Time') 28 | plt.ylabel('Real part') 29 | plt.title('Doppler') 30 | plt.xlim(0, 256) 31 | plt.grid() 32 | plt.show() 33 | -------------------------------------------------------------------------------- /doc/_gallery/plot_2_6_monocomp_nonstat_linfreq_gaussamp.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ================================================== 11 | Linear Frequency and Gaussian Amplitude Modulation 12 | ================================================== 13 | 14 | Generate a mono-component nonstationary signal with linear frequency 15 | modulation and Gaussian amplitude modulation. 16 | 17 | """ 18 | 19 | from tftb.generators import fmlin, amgauss 20 | from numpy import real 21 | import matplotlib.pyplot as plt 22 | 23 | 24 | fm, _ = fmlin(256) 25 | am = amgauss(256) 26 | signal = fm * am 27 | plt.plot(real(signal)) 28 | plt.xlabel('Time') 29 | plt.ylabel('Real part') 30 | plt.title('Linear Frequency, Gaussian Amplitude') 31 | plt.xlim(0, 256) 32 | plt.grid() 33 | plt.show() 34 | -------------------------------------------------------------------------------- /doc/_gallery/plot_2_7_multicomp_nonstat_instfreq_grpdlay.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ============================================================================================= 11 | Instantatneous Frequency and Group Delay Estimation of a Multi-Component Nonstationary Signal 12 | ============================================================================================= 13 | 14 | Figure 2.10 from the tutorial. 15 | """ 16 | 17 | # N=128; x1=fmlin(N,0,0.2); x2=fmlin(N,0.3,0.5); 18 | # x=x1+x2; 19 | # ifr=instfreq(x); subplot(211); plot(ifr); 20 | # fn=0:0.01:0.5; gd=sgrpdlay(x,fn); 21 | # subplot(212); plot(gd,fn); 22 | from tftb.generators import fmlin 23 | from tftb.processing import inst_freq, group_delay 24 | import matplotlib.pyplot as plt 25 | import numpy as np 26 | 27 | N = 128 28 | x1, _ = fmlin(N, 0, 0.2) 29 | x2, _ = fmlin(N, 0.3, 0.5) 30 | x = x1 + x2 31 | ifr = inst_freq(x)[0] 32 | fn = np.arange(0.51, step=0.01) 33 | gd = group_delay(x, fn) 34 | 35 | plt.subplot(211) 36 | plt.plot(ifr) 37 | plt.xlim(1, N) 38 | plt.grid(True) 39 | plt.title('Instantaneous Frequency') 40 | plt.xlabel('Time') 41 | plt.ylabel('Normalized Frequency') 42 | 43 | plt.subplot(212) 44 | plt.plot(gd, fn) 45 | plt.xlim(1, N) 46 | plt.grid(True) 47 | plt.title('Group Delay') 48 | plt.xlabel('Time') 49 | plt.ylabel('Normalized Frequency') 50 | 51 | plt.subplots_adjust(hspace=0.5) 52 | 53 | plt.show() 54 | -------------------------------------------------------------------------------- /doc/_gallery/plot_2_7_multicomp_nonstat_stft.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ====================================================================== 11 | Short time Fourier transform of a multi-component nonstationary signal 12 | ====================================================================== 13 | 14 | Compute and visualize the `STFT `_ of a multi component nonstationary signal. 15 | 16 | Figure 2.11 from the tutorial. 17 | """ 18 | 19 | from tftb.generators import fmlin 20 | from tftb.processing.linear import ShortTimeFourierTransform 21 | import matplotlib.pyplot as plt 22 | from scipy.signal import hamming 23 | import numpy as np 24 | from mpl_toolkits.axes_grid1 import make_axes_locatable 25 | 26 | N = 128 27 | x1, _ = fmlin(N, 0, 0.2) 28 | x2, _ = fmlin(N, 0.3, 0.5) 29 | x = x1 + x2 30 | 31 | n_fbins = 128 32 | window = hamming(33) 33 | tfr, _, _ = ShortTimeFourierTransform(x, timestamps=None, n_fbins=n_fbins, 34 | fwindow=window).run() 35 | tfr = tfr[:64, :] 36 | threshold = np.amax(np.abs(tfr)) * 0.05 37 | tfr[np.abs(tfr) <= threshold] = 0.0 + 1j * 0.0 38 | tfr = np.abs(tfr) ** 2 39 | t = np.arange(tfr.shape[1]) 40 | f = np.linspace(0, 0.5, tfr.shape[0]) 41 | 42 | T, F = np.meshgrid(t, f) 43 | 44 | fig, axScatter = plt.subplots(figsize=(10, 8)) 45 | axScatter.contour(T, F, tfr, 5) 46 | axScatter.grid(True) 47 | axScatter.set_title('Squared modulus of STFT') 48 | axScatter.set_ylabel('Frequency') 49 | axScatter.yaxis.set_label_position("right") 50 | axScatter.set_xlabel('Time') 51 | divider = make_axes_locatable(axScatter) 52 | axTime = divider.append_axes("top", 1.2, pad=0.5) 53 | axFreq = divider.append_axes("left", 1.2, pad=0.5) 54 | axTime.plot(np.real(x)) 55 | axTime.set_xticklabels([]) 56 | axTime.set_xlim(0, N) 57 | axTime.set_ylabel('Real part') 58 | axTime.set_title('Signal in time') 59 | axTime.grid(True) 60 | axFreq.plot((abs(np.fft.fftshift(np.fft.fft(x))) ** 2)[::-1][:64], f[:64]) 61 | axFreq.set_yticklabels([]) 62 | axFreq.set_xticklabels([]) 63 | axFreq.invert_xaxis() 64 | axFreq.set_ylabel('Spectrum') 65 | axFreq.grid(True) 66 | plt.show() 67 | -------------------------------------------------------------------------------- /doc/_gallery/plot_3_1_2_spectrum.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ================================== 11 | Energy Spectrum of an Audio Signal 12 | ================================== 13 | 14 | Figure 3.3 from the tutorial. 15 | """ 16 | 17 | from os.path import dirname, abspath, join 18 | from scipy.io import loadmat 19 | import numpy as np 20 | import matplotlib.pyplot as plt 21 | 22 | DATA_PATH = join(abspath(dirname("__file__")), "data", "gabor.mat") 23 | signal = loadmat(DATA_PATH)['gabor'].ravel() 24 | time = np.arange(338) 25 | dsp = np.fft.fftshift(np.abs(np.fft.fft(signal)) ** 2) 26 | freq = np.arange(-169, 169, dtype=float) / 338 * 1000 27 | 28 | plt.subplot(211) 29 | plt.plot(time, signal) 30 | plt.grid(True) 31 | plt.xlim(0, time.max()) 32 | plt.xlabel('Time (ms)') 33 | 34 | plt.subplot(212) 35 | plt.plot(dsp) 36 | plt.grid(True) 37 | plt.title('Spectrum') 38 | plt.xlabel('Frequency (Hz)') 39 | 40 | plt.subplots_adjust(hspace=0.5) 41 | 42 | plt.show() 43 | -------------------------------------------------------------------------------- /doc/_gallery/plot_3_1_2_stft.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ======================= 11 | STFT of an Audio Signal 12 | ======================= 13 | 14 | Figure 3.4 from the tutorial. 15 | """ 16 | 17 | from os.path import dirname, abspath, join 18 | from scipy.io import loadmat 19 | import numpy as np 20 | import matplotlib.pyplot as plt 21 | 22 | DATA_PATH = join(abspath(dirname("__file__")), "data", "gabor.mat") 23 | signal = loadmat(DATA_PATH)['gabor'].ravel() 24 | tfr = loadmat(DATA_PATH)['tfr'] 25 | time = np.arange(338) 26 | freq = np.arange(128, dtype=float) / 256.0 * 1000 27 | 28 | plt.contour(time, freq, tfr) 29 | plt.grid(True) 30 | plt.xlabel('Time [ms]') 31 | plt.ylabel('Frequency [Hz]') 32 | plt.title('Squared modulus of the STFT of the word GABOR') 33 | plt.show() 34 | -------------------------------------------------------------------------------- /doc/_gallery/plot_3_1_4_atoms_hamming_stft.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ============================================================ 11 | STFT of Gaussian Wave Packets with a Hamming Analysis Window 12 | ============================================================ 13 | 14 | This example demonstrates the construction of a signal containing two transient 15 | components, having the same Gaussian amplitude modulation and the same 16 | frequency, but different time centers. It also shows the effect of a Hamming 17 | window function when used with th STFT. 18 | 19 | Figure 3.7 from the tutorial. 20 | """ 21 | 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | from tftb.generators import atoms 25 | from scipy.signal import hamming 26 | from tftb.processing.linear import ShortTimeFourierTransform 27 | 28 | coords = np.array([[45, .25, 32, 1], [85, .25, 32, 1]]) 29 | sig = atoms(128, coords) 30 | x = np.real(sig) 31 | window = hamming(65) 32 | stft = ShortTimeFourierTransform(sig, n_fbins=128, fwindow=window) 33 | stft.run() 34 | stft.plot(show_tf=True, cmap=plt.cm.gray) 35 | -------------------------------------------------------------------------------- /doc/_gallery/plot_3_1_4_atoms_short_hamming_stft.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ================================================ 11 | Time-frequency Resolution: Short Analysis Window 12 | ================================================ 13 | 14 | This example shows the effect of an analysis window which is short in time on 15 | the time-frequency resolution. Specifically, smaller windows have good time 16 | resolutions but poor frequency resolutions. 17 | 18 | Figure 3.8 from the tutorial. 19 | """ 20 | 21 | import numpy as np 22 | import matplotlib.pyplot as plt 23 | from tftb.generators import atoms 24 | from scipy.signal import hamming 25 | from tftb.processing.linear import ShortTimeFourierTransform 26 | 27 | coords = np.array([[45, .25, 32, 1], [85, .25, 32, 1]]) 28 | sig = atoms(128, coords) 29 | x = np.real(sig) 30 | window = hamming(17) 31 | stft = ShortTimeFourierTransform(sig, n_fbins=128, fwindow=window) 32 | stft.run() 33 | stft.plot(show_tf=True, cmap=plt.cm.gray) 34 | -------------------------------------------------------------------------------- /doc/_gallery/plot_3_1_4_frequency_resolution.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ================================================ 11 | Time-frequency Resolution: Long Analysis Window 12 | ================================================ 13 | 14 | This example shows the effect of an analysis window which is long in time on 15 | the time-frequency resolution. Specifically, longer windows have good frequency 16 | resolutions but poor time resolutions. 17 | 18 | Figure 3.6 from the tutorial. 19 | """ 20 | 21 | import numpy as np 22 | from tftb.processing.linear import ShortTimeFourierTransform 23 | from tftb.generators import fmlin, amgauss 24 | import matplotlib.pyplot as plt 25 | 26 | x = np.real(amgauss(128) * fmlin(128)[0]) 27 | window = np.ones((128,)) 28 | stft = ShortTimeFourierTransform(x, n_fbins=128, fwindow=window) 29 | stft.run() 30 | stft.plot(show_tf=True, cmap=plt.cm.gray) 31 | -------------------------------------------------------------------------------- /doc/_gallery/plot_3_1_4_time_resolution.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ===================== 11 | Ideal time resolution 12 | ===================== 13 | 14 | This example demonstrates that only the shortest possible window can provide 15 | ideal resolution in time. 16 | 17 | Figure 3.5 from the tutorial. 18 | """ 19 | 20 | import numpy as np 21 | from tftb.processing.linear import ShortTimeFourierTransform 22 | from tftb.generators import fmlin, amgauss 23 | from matplotlib.pyplot import cm 24 | 25 | x = np.real(amgauss(128) * fmlin(128)[0]) 26 | window = np.array([1]) 27 | stft = ShortTimeFourierTransform(x, n_fbins=128, fwindow=window) 28 | tfr, _, _ = stft.run() 29 | 30 | stft.plot(show_tf=True, cmap=cm.gray) 31 | -------------------------------------------------------------------------------- /doc/_gallery/plot_3_3_2_biorthonormal_window.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ============================= 11 | Biorthonormal Window Function 12 | ============================= 13 | 14 | Figure 3.10 from the tutorial. 15 | """ 16 | 17 | from tftb.generators import fmlin 18 | from tftb.processing.linear import gabor 19 | import matplotlib.pyplot as plt 20 | import numpy as np 21 | 22 | N1 = 256 23 | Ng = 33 24 | Q = 1 25 | sig = fmlin(N1)[0] 26 | window = np.exp(np.log(0.005) * np.linspace(-1, 1, Ng) ** 2) 27 | window = window / np.linalg.norm(window) 28 | tfr, dgr, h = gabor(sig, 16, Q, window) 29 | plt.plot(h) 30 | plt.ylim(top=0.5) 31 | plt.xlim(right=255) 32 | plt.title('Biorthonormal Window') 33 | plt.grid() 34 | plt.show() 35 | -------------------------------------------------------------------------------- /doc/_gallery/plot_3_3_2_biorthonormal_window_gabor.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | =============================== 11 | Gabor Representation of a Chirp 12 | =============================== 13 | 14 | Figure 3.11 from the tutorial. 15 | """ 16 | 17 | from tftb.generators import fmlin 18 | from tftb.processing.linear import gabor 19 | import matplotlib.pyplot as plt 20 | import numpy as np 21 | 22 | N1 = 256 23 | Ng = 33 24 | Q = 1 25 | sig = fmlin(N1)[0] 26 | window = np.exp(np.log(0.005) * np.linspace(-1, 1, Ng) ** 2) 27 | window = window / np.linalg.norm(window) 28 | tfr, dgr, h = gabor(sig, 16, Q, window) 29 | 30 | plt.imshow(np.flipud(tfr)[8:, :], aspect='auto', extent=[0, 16, 0, 0.5], 31 | interpolation='none') 32 | plt.xlabel('Time') 33 | plt.ylabel('Normalized frequency') 34 | plt.title('Squared modulus of the Gabor coefficients') 35 | plt.show() 36 | -------------------------------------------------------------------------------- /doc/_gallery/plot_3_3_2_biorthonormal_window_oversampled.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ================================================= 11 | Gabor Representation of a Chirp with Oversampling 12 | ================================================= 13 | 14 | Figure 3.13 from the tutorial. 15 | """ 16 | 17 | from tftb.generators import fmlin 18 | from tftb.processing.linear import gabor 19 | import matplotlib.pyplot as plt 20 | import numpy as np 21 | 22 | N1 = 256 23 | Ng = 33 24 | Q = 4 25 | sig = fmlin(N1)[0] 26 | window = np.exp(np.log(0.005) * np.linspace(-1, 1, Ng) ** 2) 27 | window = window / np.linalg.norm(window) 28 | tfr, dgr, h = gabor(sig, 32, Q, window) 29 | time = np.arange(256) 30 | freq = np.linspace(0, 0.5, 128) 31 | plt.imshow(np.flipud(tfr)[8:, :], aspect='auto', extent=[0, 32, 0, 0.5], 32 | interpolation='none') 33 | plt.xlabel('Time') 34 | plt.ylabel('Normalized frequency') 35 | plt.title('Squared modulus of the Gabor coefficients') 36 | plt.show() 37 | -------------------------------------------------------------------------------- /doc/_gallery/plot_3_4_1_chirps_spectrogram_long_gaussian.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | =================================================================== 11 | Spectrogram of Parallel Chirps with a Long Gaussian Analysis Window 12 | =================================================================== 13 | 14 | This example visualizes the spectrogram of two "parallel" chirps, using a 15 | Gaussian window function that has a long length, relative to the length of a 16 | signal. The two chirps can be made out, but interference can also be seen along 17 | the time axis, since time resolution is compromised. 18 | 19 | Figure 3.16 from the tutorial. 20 | """ 21 | 22 | from tftb.generators import fmlin 23 | from tftb.processing.cohen import Spectrogram 24 | import numpy as np 25 | import matplotlib.pyplot as plt 26 | 27 | sig = fmlin(128, 0, 0.4)[0] + fmlin(128, 0.1, 0.5)[0] 28 | window = np.exp(np.log(0.005) * np.linspace(-1, 1, 63) ** 2) 29 | spec = Spectrogram(sig, fwindow=window, n_fbins=128) 30 | spec.run() 31 | spec.plot(show_tf=True, cmap=plt.cm.gray) 32 | -------------------------------------------------------------------------------- /doc/_gallery/plot_3_4_1_chirps_spectrogram_short_gaussian.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ================================================================== 11 | Spectrogram of Parallel Chirps with Short Gaussian Analysis Window 12 | ================================================================== 13 | 14 | This example visualizes the spectrogram of two "parallel" chirps, using a 15 | Gaussian window function that has a short length, relative to the length of a 16 | signal. The two chirps can be made out, but interference can also be seen along 17 | the frequency axis, since frequency resolution is compromised. 18 | 19 | Figure 3.15 from the tutorial. 20 | """ 21 | 22 | from tftb.generators import fmlin 23 | from tftb.processing.cohen import Spectrogram 24 | import numpy as np 25 | import matplotlib.pyplot as plt 26 | 27 | sig = fmlin(128, 0, 0.4)[0] + fmlin(128, 0.1, 0.5)[0] 28 | window = np.exp(np.log(0.005) * np.linspace(-1, 1, 23) ** 2) 29 | spec = Spectrogram(sig, fwindow=window, n_fbins=128) 30 | spec.run() 31 | spec.plot(show_tf=True, cmap=plt.cm.gray) 32 | -------------------------------------------------------------------------------- /doc/_gallery/plot_3_4_1_distant_components_long_gaussian.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | =================================================== 11 | Distant Chirps with a Long Gaussian Analysis Window 12 | =================================================== 13 | 14 | This example visualizes a spectrogram of two chirp signals which are well 15 | separated in frequency ranges. A longer Gaussian analysis window suffices here 16 | to see the separation of frequencies, since the variation in frequencies is 17 | relatively slow. 18 | 19 | Figure 3.18 from the tutorial. 20 | """ 21 | 22 | from tftb.generators import fmlin 23 | from tftb.processing.cohen import Spectrogram 24 | import numpy as np 25 | import matplotlib.pyplot as plt 26 | 27 | sig = fmlin(128, 0, 0.3)[0] + fmlin(128, 0.2, 0.5)[0] 28 | window = np.exp(np.log(0.005) * np.linspace(-1, 1, 63) ** 2) 29 | spec = Spectrogram(sig, fwindow=window, n_fbins=128) 30 | spec.run() 31 | spec.plot(show_tf=True, cmap=plt.cm.gray) 32 | -------------------------------------------------------------------------------- /doc/_gallery/plot_3_4_1_distant_components_short_gaussian.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ==================================================== 11 | Distant Chirps with a Short Gaussian Analysis Window 12 | ==================================================== 13 | 14 | Figure 3.17 from the tutorial. 15 | """ 16 | 17 | from tftb.generators import fmlin 18 | from tftb.processing.cohen import Spectrogram 19 | import numpy as np 20 | import matplotlib.pyplot as plt 21 | 22 | sig = fmlin(128, 0, 0.3)[0] + fmlin(128, 0.2, 0.5)[0] 23 | window = np.exp(np.log(0.005) * np.linspace(-1, 1, 23) ** 2) 24 | spec = Spectrogram(sig, fwindow=window, n_fbins=128) 25 | spec.run() 26 | spec.plot(show_tf=True, cmap=plt.cm.gray) 27 | -------------------------------------------------------------------------------- /doc/_gallery/plot_3_4_2_morlet_scalogram_complex_sinusoids.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | =========================================== 11 | Morlet Scalogram of a Multicomponent Signal 12 | =========================================== 13 | 14 | This example demonstrates the visualization of the Morlet scalogram of a signal 15 | containing two complex sinusoids. In a scalogram, the frequency resolution 16 | varies on the scale of the signal. Here, the frequency resolution decreases at 17 | higher frequencies (lower scale). 18 | 19 | Figure 3.20 from the tutorial. 20 | """ 21 | 22 | from tftb.processing import Scalogram 23 | from tftb.generators import fmconst 24 | import numpy as np 25 | from mpl_toolkits.axes_grid1 import make_axes_locatable 26 | import matplotlib.pyplot as plt 27 | 28 | sig2 = fmconst(128, .15)[0] + fmconst(128, .35)[0] 29 | tfr, t, freqs, _ = Scalogram(sig2, time_instants=np.arange(1, 129), waveparams=6, 30 | fmin=0.05, fmax=0.45, n_voices=128).run() 31 | tfr = np.abs(tfr) ** 2 32 | threshold = np.amax(tfr) * 0.05 33 | tfr[tfr <= threshold] = 0.0 34 | t, f = np.meshgrid(t, freqs) 35 | 36 | fig, axContour = plt.subplots(figsize=(10, 8)) 37 | axContour.contour(t, f, tfr) 38 | axContour.grid(True) 39 | axContour.set_title("Morlet scalogram") 40 | axContour.set_ylabel('Frequency') 41 | axContour.yaxis.set_label_position('right') 42 | axContour.set_xlabel('Time') 43 | 44 | divider = make_axes_locatable(axContour) 45 | axTime = divider.append_axes("top", 1.2, pad=0.5) 46 | axFreq = divider.append_axes("left", 1.2, pad=0.5) 47 | axTime.plot(np.real(sig2)) 48 | axTime.set_xticklabels([]) 49 | axTime.set_xlim(0, 128) 50 | axTime.set_ylabel('Real part') 51 | axTime.set_title('Signal in time') 52 | axTime.grid(True) 53 | freq_y = np.linspace(0, 0.5, int(sig2.shape[0] / 2)) 54 | freq_x = (abs(np.fft.fftshift(np.fft.fft(sig2))) ** 2)[::-1][:64] 55 | axFreq.plot(freq_x, freq_y) 56 | axFreq.set_ylim(0.05, 0.45) 57 | axFreq.set_yticklabels([]) 58 | axFreq.set_xticklabels([]) 59 | axFreq.grid(True) 60 | axFreq.set_ylabel('Spectrum') 61 | axFreq.invert_xaxis() 62 | axFreq.grid(True) 63 | plt.show() 64 | -------------------------------------------------------------------------------- /doc/_gallery/plot_3_4_2_morlet_scalogram_dirac_impulse.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | =================================== 11 | Morlet Scalogram of a Dirac Impulse 12 | =================================== 13 | 14 | This example plots the scalogram of a Dirac impulse functions. This shows the 15 | behaviour of the scalograms as the scale (or inversely, the frequency) changes. 16 | it is well localized for small scales (large frequencies), and less localized 17 | as the scale increases (as the frequency decreases). 18 | 19 | Figure 3.19 from the tutorial. 20 | """ 21 | 22 | from tftb.generators import anapulse 23 | from tftb.processing import Scalogram 24 | import numpy as np 25 | import matplotlib.pyplot as plt 26 | 27 | sig1 = anapulse(128) 28 | tfr, t, f, _ = Scalogram(sig1, waveparams=6, fmin=0.05, fmax=0.45, 29 | n_voices=128).run() 30 | tfr = np.abs(tfr) ** 2 31 | threshold = np.amax(tfr) * 0.05 32 | tfr[tfr <= threshold] = 0.0 33 | t, f = np.meshgrid(t, f) 34 | plt.contour(t, f, tfr, 20) 35 | plt.grid() 36 | plt.title('Morlet Scalogram of a Dirac Impluse') 37 | plt.xlabel('Time') 38 | plt.ylabel('Normalized Frequency') 39 | plt.show() 40 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_1_1_doppler_wigner_ville.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ============================================= 11 | Wigner-Ville Distribution of a Doppler Signal 12 | ============================================= 13 | 14 | This example shows the Wigner-Ville distribution of a Doppler signal. The 15 | signal steadily rises and falls, but there are many interference terms present 16 | in the time-friequency plane, due to the bilinearity of the signal. 17 | 18 | Figure 4.2 from the tutorial. 19 | """ 20 | 21 | from tftb.generators import doppler 22 | from tftb.processing import WignerVilleDistribution 23 | 24 | fm, am, iflaw = doppler(256, 50.0, 13.0, 10.0, 200.0) 25 | sig = am * fm 26 | dist = WignerVilleDistribution(sig) 27 | tfr, times, freqs = dist.run() 28 | dist.plot(show_tf=True, kind="contour", scale="log") 29 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_1_1_pwv_atoms.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ================================================== 11 | Pseudo Wigner-Ville Distribution of Gaussian Atoms 12 | ================================================== 13 | 14 | This example shows the Pseudo Wigner-Ville distribution of four Gaussian atoms 15 | located at the corners of a rectangle in the time-frequency plane. The 16 | `PseudoWignerVilleDistribution` class uses frequency smoothing, which 17 | attenuates the interferences oscillating along the time axis. 18 | 19 | Figure 4.5 from the tutorial. 20 | """ 21 | 22 | import numpy as np 23 | from tftb.generators import atoms 24 | from tftb.processing import PseudoWignerVilleDistribution 25 | 26 | x = np.array([[32, .15, 20, 1], 27 | [96, .15, 20, 1], 28 | [32, .35, 20, 1], 29 | [96, .35, 20, 1]]) 30 | g = atoms(128, x) 31 | t = np.linspace(0, 1, 128) 32 | spec = PseudoWignerVilleDistribution(g, timestamps=t) 33 | spec.run() 34 | spec.plot(kind="contour", scale="log", show_tf=True) 35 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_1_1_wv_analytic_signal.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ==================================================== 11 | Wigner Ville Distribution of Analytic Gaussian Atoms 12 | ==================================================== 13 | 14 | This example shows the WVD of and analytic Gaussian atom. As seen in Figure 15 | 4.6, the WVD of a real valued signal may present interference terms and 16 | spectral aliasing. One of the ways to fix this is to use an analytic signal, 17 | which divides the spectral domain into two parts: real and imaginary. Thus, the 18 | number of interference terms is also halved. Secondly, analytic signals have no 19 | negative components, so the terms present in the negative half plane also 20 | vanish. 21 | 22 | Figure 4.7 from the tutorial. 23 | """ 24 | 25 | import numpy as np 26 | from tftb.generators import atoms 27 | from tftb.processing import WignerVilleDistribution 28 | 29 | x = np.array([[32, .15, 20, 1], 30 | [96, .32, 20, 1]]) 31 | g = atoms(128, x) 32 | spec = WignerVilleDistribution(g) 33 | spec.run() 34 | spec.plot(show_tf=True, kind="contour", scale="log") 35 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_1_1_wv_atoms.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | =========================================== 11 | Wigner-Ville Distribution of Gaussian Atoms 12 | =========================================== 13 | 14 | This example shows the WV distribution of four Gaussian atoms, each localized 15 | at the corner of a rectangle in the time-frequency plane. The distribution does 16 | show the four signal terms, as well as six interference terms. 17 | 18 | Figure 4.4 from the tutorial. 19 | """ 20 | 21 | import numpy as np 22 | from tftb.generators import atoms 23 | from tftb.processing import WignerVilleDistribution 24 | 25 | x = np.array([[32, .15, 20, 1], 26 | [96, .15, 20, 1], 27 | [32, .35, 20, 1], 28 | [96, .35, 20, 1]]) 29 | g = atoms(128, x) 30 | spec = WignerVilleDistribution(g) 31 | spec.run() 32 | spec.plot(kind="contour", show_tf=True, scale="log") 33 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_1_1_wv_real_signal.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ================================================================================ 11 | Sampling Effects on the Wigner-Ville Distribution of a Real Valued Gaussian Atom 12 | ================================================================================ 13 | 14 | This example shows the Wigner-Ville distribution of a real valued Gaussian 15 | atom. If a signal is sampled at the Nyquist rate, the WVD is affected by 16 | spectral aliasing and many additional interferences. To fix this, either the 17 | signal may be oversampled, or an analytical signal may be used. 18 | 19 | Figure 4.6 from the tutorial. 20 | """ 21 | 22 | import numpy as np 23 | from tftb.generators import atoms 24 | from tftb.processing import WignerVilleDistribution 25 | 26 | x = np.array([[32, .15, 20, 1], 27 | [96, .32, 20, 1]]) 28 | g = atoms(128, x) 29 | spec = WignerVilleDistribution(np.real(g)) 30 | spec.run() 31 | spec.plot(kind="contour", show_tf=True, scale="log") 32 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_1_1_wv_wireframe.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ==================================== 11 | Wigner-Ville distribution of a Chirp 12 | ==================================== 13 | 14 | This example shows the wireframe plot of the Wigner-Ville distribution of a 15 | chirp. The WV distribution can take negative values, and has almost perfect 16 | localization in the time-frequency plane. 17 | 18 | Figure 4.1 from the tutorial. 19 | """ 20 | 21 | from tftb.generators import fmlin 22 | from tftb.processing import WignerVilleDistribution 23 | 24 | sig = fmlin(256)[0] 25 | tfr = WignerVilleDistribution(sig) 26 | tfr.run() 27 | tfr.plot(threshold=0.0, kind='wireframe') 28 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_1_2_sin_gauss_pwv.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ========================================================================== 11 | Pseudo-Wigner-Ville Distribution of a Gaussian Atom and a Complex Sinusoid 12 | ========================================================================== 13 | 14 | This example demonstrates the pseudo Wigner Ville distribution of a signal 15 | composed from a Gaussian atom and a complex sinusoid with constant frequency 16 | modulation. Note that the frequency resolution is relatively worse than that of 17 | the Wigner-Ville representation, and the interferences have not been resolved 18 | properly. 19 | 20 | Figure 4.9 from the tutorial. 21 | """ 22 | 23 | from tftb.generators import fmconst, amgauss 24 | from tftb.processing import PseudoWignerVilleDistribution 25 | import numpy as np 26 | 27 | t = np.linspace(0, 1, 128) 28 | sig = fmconst(128, 0.15)[0] + amgauss(128) * fmconst(128, 0.4)[0] 29 | tfr = PseudoWignerVilleDistribution(sig, timestamps=t) 30 | tfr.run() 31 | tfr.plot(show_tf=True, kind="contour") 32 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_1_2_sin_gauss_wv.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | =================================================================== 11 | Wigner Ville distribution of a Gaussian Atom and a Complex Sinusoid 12 | =================================================================== 13 | 14 | This example demonstrates the Wigner Ville distribution of a signal 15 | composed from a Gaussian atom and a complex sinusoid with constant frequency 16 | modulation. Although the representation does isolate the atom and the sinusoid 17 | as independent phenomena in the signal, it also produces some interference 18 | between them. 19 | 20 | Figure 4.8 from the tutorial. 21 | """ 22 | 23 | from tftb.generators import fmconst, amgauss 24 | from tftb.processing import WignerVilleDistribution 25 | 26 | sig = fmconst(128, 0.15)[0] + amgauss(128) * fmconst(128, 0.4)[0] 27 | tfr = WignerVilleDistribution(sig) 28 | tfr.run() 29 | tfr.plot(show_tf=True, kind='contour') 30 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_1_3_chirps_ambifunb.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ============================================================== 11 | Narrow Band Ambiguity Function of Chirps with Different Slopes 12 | ============================================================== 13 | 14 | This example demonstrates the narrow band ambiguity function (AF) of a signal 15 | composed of two chirps with Gaussian amplitude modulation but havind linear 16 | frequency modulations with different slopes. Note that the AF interference 17 | terms are located away from the origin. 18 | 19 | Figure 4.13 from the tutorial. 20 | """ 21 | 22 | from tftb.generators import fmlin, amgauss 23 | from tftb.processing.ambiguity import narrow_band 24 | import numpy as np 25 | import matplotlib.pyplot as plt 26 | 27 | n_points = 64 28 | sig1 = fmlin(n_points, 0.2, 0.5)[0] * amgauss(n_points) 29 | sig2 = fmlin(n_points, 0.3, 0)[0] * amgauss(n_points) 30 | sig = np.hstack((sig1, sig2)) 31 | 32 | tfr, x, y = narrow_band(sig) 33 | plt.contour(2 * x, y, np.abs(tfr) ** 2, 16) 34 | plt.title('Narrow Band ambiguity function') 35 | plt.xlabel('Delay') 36 | plt.ylabel('Doppler') 37 | plt.grid(True) 38 | plt.show() 39 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_1_3_chirps_wvd.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ========================================================= 11 | Wigner-Ville Distribution of Chirps with Different Slopes 12 | ========================================================= 13 | 14 | This example demonstrates the Wigner-Ville distribution of a signal 15 | composed of two chirps with Gaussian amplitude modulation but havind linear 16 | frequency modulations with different slopes. Note that the AF interference 17 | terms are located away from the origin. We can see the two distint signal 18 | terms, but there is some interference around the middle. 19 | 20 | Figure 4.12 from the tutorial. 21 | """ 22 | 23 | from tftb.generators import fmlin, amgauss 24 | from tftb.processing import WignerVilleDistribution 25 | import numpy as np 26 | 27 | n_points = 64 28 | sig1 = fmlin(n_points, 0.2, 0.5)[0] * amgauss(n_points) 29 | sig2 = fmlin(n_points, 0.3, 0)[0] * amgauss(n_points) 30 | sig = np.hstack((sig1, sig2)) 31 | 32 | tfr = WignerVilleDistribution(sig) 33 | tfr.run() 34 | tfr.plot(kind='contour', show_tf=True) 35 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_1_4_margenau_hill.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ============================================================ 11 | Margenau-Hill Representation of Chirps with Different Slopes 12 | ============================================================ 13 | 14 | This example demonstrates the Margenau-Hill distribution of a signal 15 | composed of two chirps with Gaussian amplitude modulation but havind linear 16 | frequency modulations with different slopes. This distribution too, like the 17 | Wigner-Ville distribution, spearates the signal terms, but produces 18 | interferences such that they appear diagonally opposite their corresponding 19 | signals. 20 | 21 | Figure 4.14 from the tutorial. 22 | """ 23 | 24 | import numpy as np 25 | from tftb.generators import atoms 26 | from tftb.processing import MargenauHillDistribution 27 | 28 | 29 | sig = atoms(128, np.array([[32, 0.15, 20, 1], [96, 0.32, 20, 1]])) 30 | tfr = MargenauHillDistribution(sig) 31 | tfr.run() 32 | tfr.plot(show_tf=True, kind='contour', sqmod=False, threshold=0, 33 | contour_y=np.linspace(0, 0.5, int(tfr.tfr.shape[0] / 2))) 34 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_2_2_bertrand_hyperbolic_gd.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ======================================================== 11 | Bertrand Distribution of a Hyperbolic Group Delay Signal 12 | ======================================================== 13 | 14 | This example shows the Bertrand distribution of a signal with hyperbolic group 15 | delay. The distribution is well localized around the hyperbola, but not 16 | perfectly. The Bertrand distribution operates only on a part of the frequency 17 | range between two bounds :math:`f_{min}` and :math:`f_{max}`. 18 | 19 | Figure 4.21 from the tutorial. 20 | """ 21 | 22 | from tftb.processing.affine import BertrandDistribution 23 | from tftb.generators import gdpower 24 | 25 | 26 | sig = gdpower(128)[0] 27 | bert = BertrandDistribution(sig, fmin=0.01, fmax=0.22) 28 | bert.run() 29 | bert.plot() 30 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_2_2_dflandrin_hyperbolic_gd.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ========================================================== 11 | D-Flandrin Distribution of a Hyperbolic Group Delay Signal 12 | ========================================================== 13 | 14 | This example shows the D-Flandrin distribution of a signal having hyperbolic 15 | group delay. This is the only type of distribution that almost perfectly 16 | localizes signals having a group delay in :math:`1 / \sqrt{\\nu}` 17 | 18 | Figure 4.22 from the tutorial. 19 | """ 20 | 21 | from tftb.processing import DFlandrinDistribution 22 | from tftb.generators import gdpower 23 | 24 | sig = gdpower(128, 1.0 / 2)[0] 25 | spec = DFlandrinDistribution(sig, fmin=0.01, fmax=0.22, n_voices=128) 26 | spec.run() 27 | spec.plot() 28 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_2_2_morlet_scalogram_atoms.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ================================== 11 | Morlet Scalogram of Gaussian Atoms 12 | ================================== 13 | 14 | This example demonstrates the effect of frequency-dependent smoothing that is 15 | accomplished in a Morlet scalogram. Note that the localization at lower 16 | frequencies is much better. 17 | 18 | Figure 4.18 from the tutorial. 19 | 20 | """ 21 | 22 | from tftb.processing import Scalogram 23 | from tftb.generators import atoms 24 | import numpy as np 25 | from mpl_toolkits.axes_grid1 import make_axes_locatable 26 | import matplotlib.pyplot as plt 27 | 28 | sig = atoms(128, np.array([[38, 0.1, 32, 1], [96, 0.35, 32, 1]])) 29 | tfr, t, freqs, _ = Scalogram(sig, fmin=0.05, fmax=0.45, 30 | time_instants=np.arange(1, 129)).run() 31 | t, f = np.meshgrid(t, freqs) 32 | 33 | fig, axContour = plt.subplots() 34 | axContour.contour(t, f, tfr) 35 | axContour.grid(True) 36 | axContour.set_title("Morlet scalogram of complex sinusoids") 37 | axContour.set_ylabel('Frequency') 38 | axContour.yaxis.set_label_position('right') 39 | axContour.set_xlabel('Time') 40 | 41 | divider = make_axes_locatable(axContour) 42 | axTime = divider.append_axes("top", 1.2, pad=0.5) 43 | axFreq = divider.append_axes("left", 1.2, pad=0.5) 44 | axTime.plot(np.real(sig)) 45 | axTime.set_xticklabels([]) 46 | axTime.set_xlim(0, 128) 47 | axTime.set_ylabel('Real part') 48 | axTime.set_title('Signal in time') 49 | axTime.grid(True) 50 | freq_y = np.linspace(0, 0.5, int(sig.shape[0] / 2)) 51 | freq_x = (abs(np.fft.fftshift(np.fft.fft(sig))) ** 2)[::-1][:64] 52 | ix = np.logical_and(freq_y >= 0.05, freq_y <= 0.45) 53 | axFreq.plot(freq_x[ix], freq_y[ix]) 54 | # axFreq.set_ylim(0.05, 0.45) 55 | axFreq.set_yticklabels([]) 56 | axFreq.set_xticklabels([]) 57 | axFreq.grid(True) 58 | axFreq.set_ylabel('Spectrum') 59 | axFreq.invert_xaxis() 60 | axFreq.grid(True) 61 | plt.show() 62 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_2_2_unterberger_hyperbolic_gd.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | =========================================================== 11 | Unterberger distribution of a hyperbolic group delay signal 12 | =========================================================== 13 | 14 | The active Unterberger distribution is the only localized bi-frequency kernel 15 | distribution which localizes perfectly signals having a group delay in 16 | :math:`1/\\nu^{2}` 17 | 18 | Figure 4.23 from the tutorial. 19 | """ 20 | 21 | from tftb.processing import UnterbergerDistribution 22 | from tftb.generators import gdpower 23 | 24 | sig = gdpower(128, -1)[0] 25 | dist = UnterbergerDistribution(sig, fmin=0.01, fmax=0.22, n_voices=172) 26 | dist.run() 27 | dist.plot() 28 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_2_3_impulse_wv.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ===================================================== 11 | Wigner-Ville Distribution of a Dirac Impulse Function 12 | ===================================================== 13 | 14 | This example demonstrates the Wigner-Ville distribution of a Dirac impulse 15 | function, and shows the limitations of the WV distribution when applied to 16 | broadband signals. 17 | 18 | Figure 4.24 from the tutorial. 19 | """ 20 | 21 | from tftb.generators import anapulse 22 | from tftb.processing import WignerVilleDistribution 23 | 24 | sig = anapulse(128) 25 | wvd = WignerVilleDistribution(sig) 26 | wvd.run() 27 | wvd.plot(kind="contour", scale="log") 28 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_2_3_wideband_ambiguity.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ============================================== 11 | Wideband Ambiguity Function of an Altes Signal 12 | ============================================== 13 | 14 | For wideband signals, the narrow band ambiguity function is not appropriate for 15 | wideband signals. So we consider a wide band ambiguity function. This function 16 | is equivalent to the wavelet transform of a signal whose mother wavelet is the 17 | signal itself. 18 | 19 | Figure 4.25 from the tutorial. 20 | 21 | """ 22 | 23 | from tftb.generators import altes 24 | from tftb.processing.ambiguity import wide_band 25 | import matplotlib.pyplot as plt 26 | import numpy as np 27 | 28 | signal = altes(128, 0.1, 0.45) 29 | waf, tau, theta = wide_band(signal, 0.1, 0.35, 64) 30 | plt.contour(tau, theta, np.abs(waf) ** 2) 31 | plt.xlabel("Delay") 32 | plt.ylabel("Log(Scale)") 33 | plt.title("Wide Band Ambiguity Function") 34 | plt.show() 35 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_3_2_reassigned_spectrogram.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ======================================================== 11 | Comparison of a Spectrogram and a Reassigned Spectrogram 12 | ======================================================== 13 | 14 | This example compares the spectrogram and the reassigned spectrogram of a 15 | hybrid signal (containing sinusoidal, constant and linear frequency 16 | modulations), against its ideal time-frequency characteristics. 17 | 18 | Figure 4.34 from the tutorial. 19 | """ 20 | 21 | from tftb.generators import fmsin, fmhyp 22 | from tftb.processing import ideal_tfr, reassigned_spectrogram, Spectrogram 23 | import numpy as np 24 | import matplotlib.pyplot as plt 25 | 26 | n_points = 128 27 | sig1, if1 = fmsin(n_points, 0.15, 0.45, 100, 1, 0.4, -1) 28 | sig2, if2 = fmhyp(n_points, [1, .5], [32, 0.05]) 29 | sig = sig1 + sig2 30 | ideal, t, f = ideal_tfr(np.vstack((if1, if2))) 31 | _, re_spec, _ = reassigned_spectrogram(sig) 32 | spec, t3, f3 = Spectrogram(sig).run() 33 | 34 | # Ideal tfr 35 | plt.subplot(221) 36 | plt.contour(t, f, ideal, 1) 37 | plt.grid(True) 38 | plt.gca().set_xticklabels([]) 39 | plt.title("Ideal time-frequency distro") 40 | plt.ylabel('Normalized Frequency') 41 | 42 | # Spectrogram 43 | plt.subplot(222) 44 | plt.contour(t3, f3[:64], spec[:64, :]) 45 | plt.grid(True) 46 | plt.gca().set_xticklabels([]) 47 | plt.title("Spectrogram") 48 | 49 | # Reassigned Spectrogram 50 | plt.subplot(212) 51 | f = np.linspace(0, 0.5, 64) 52 | plt.contour(np.arange(128), f, re_spec[:64, :]) 53 | plt.grid(True) 54 | plt.title("Reassigned Spectrogram") 55 | plt.xlabel('Time') 56 | plt.ylabel('Normalized Frequency') 57 | 58 | plt.show() 59 | -------------------------------------------------------------------------------- /doc/_gallery/plot_4_3_6_friedman_instfreq_density.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ====================================================== 11 | Friedman's Instantaneous Frequency Density Calculation 12 | ====================================================== 13 | 14 | This example uses Friedman's method to calculate the instantaneous frequency 15 | density of a hybrid signal. The method consists of computing the histograms of 16 | frequency displacements of the spectrogram of the signal. 17 | 18 | Figure 4.38 from the tutorial. 19 | """ 20 | 21 | import numpy as np 22 | import matplotlib.pyplot as plt 23 | from tftb.generators import fmlin, fmsin, fmconst 24 | from tftb.processing.reassigned import pseudo_wigner_ville 25 | from tftb.processing.postprocessing import friedman_density 26 | 27 | sig1, if1 = fmsin(60, 0.16, 0.35, 50, 1, 0.35, 1) 28 | sig2, if2 = fmlin(60, 0.3, 0.1) 29 | sig3, if3 = fmconst(60, 0.4) 30 | sig = np.hstack((sig1, np.zeros((8,)), sig2 + sig3)) 31 | 32 | t = np.arange(1, 128, step=2) 33 | tfr, rtfr, hat = pseudo_wigner_ville(sig, timestamps=t) 34 | tifd = friedman_density(tfr, hat, t) 35 | f = np.linspace(0, 0.5, tifd.shape[0]) 36 | 37 | plt.contour(t, f, tifd, 4) 38 | plt.grid(True) 39 | plt.title("Friedman's instantaenous frequency density") 40 | plt.xlabel('Time') 41 | plt.ylabel('Frequency') 42 | plt.show() 43 | -------------------------------------------------------------------------------- /doc/_gallery/plot_5_4_2_hough_noisy_chirp.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ================================= 11 | Wigner-Hough Transform of a Chirp 12 | ================================= 13 | 14 | This example demonstrates the use of the Hough transform to extract the 15 | estimates of a chirp signal from its Wigner Ville distribution. 16 | 17 | Figure 5.4 from the tutorial. 18 | """ 19 | 20 | import numpy as np 21 | from tftb.generators import noisecg, sigmerge, fmlin 22 | from tftb.processing.cohen import WignerVilleDistribution 23 | from tftb.processing.postprocessing import hough_transform 24 | import matplotlib.pyplot as plt 25 | from mpl_toolkits.mplot3d import Axes3D 26 | 27 | N = 64 28 | sig = sigmerge(fmlin(N, 0, 0.3)[0], noisecg(N), 1) 29 | tfr, _, _ = WignerVilleDistribution(sig).run() 30 | 31 | ht, rho, theta = hough_transform(tfr, N, N) 32 | theta, rho = np.meshgrid(theta, rho) 33 | fig = plt.figure() 34 | ax = fig.gca(projection='3d') 35 | ax.plot_wireframe(theta, rho, ht) 36 | ax.set_xlabel('Theta') 37 | ax.set_ylabel('Rho') 38 | plt.show() 39 | -------------------------------------------------------------------------------- /doc/_gallery/plot_5_4_2_hough_simultaneous_chirp.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ================================================= 11 | Hough-Wigner Transform of Two Simultaneous Chirps 12 | ================================================= 13 | 14 | Compute the Hough transform of the Wigner-Ville distribution of a signal 15 | composed of two chirps. Two peaks corresponding to the two chirps can be seen. 16 | 17 | Figure 5.6 from the tutorial. 18 | """ 19 | 20 | from tftb.generators import fmlin, sigmerge 21 | from tftb.processing.cohen import WignerVilleDistribution 22 | from tftb.processing.postprocessing import hough_transform 23 | import numpy as np 24 | from mpl_toolkits.mplot3d import Axes3D 25 | import matplotlib.pyplot as plt 26 | 27 | N = 64 28 | sig = sigmerge(fmlin(N, 0, 0.4)[0], fmlin(N, 0.3, 0.5)[0], 1) 29 | tfr, _, _ = WignerVilleDistribution(sig).run() 30 | 31 | ht, rho, theta = hough_transform(tfr, N, N) 32 | theta, rho = np.meshgrid(theta, rho) 33 | fig = plt.figure() 34 | ax = fig.gca(projection='3d') 35 | ax.plot_wireframe(theta, rho, ht) 36 | ax.set_xlabel('Theta') 37 | ax.set_ylabel('Rho') 38 | plt.show() 39 | -------------------------------------------------------------------------------- /doc/_gallery/plot_5_4_2_wv_noisy_chirp.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ========================================== 11 | Wigner-Ville Distribution of a Noisy Chirp 12 | ========================================== 13 | 14 | This example shows the Wigner-Ville distribution of a noisy chirp signal. The 15 | linear frequency increase is undetectable in the time domain, but a straight 16 | line can be seen in the distribution. 17 | 18 | Figure 5.3 from the tutorial. 19 | """ 20 | 21 | from tftb.generators import noisecg, sigmerge, fmlin 22 | from tftb.processing.cohen import WignerVilleDistribution 23 | 24 | N = 64 25 | sig = sigmerge(fmlin(N, 0, 0.3)[0], noisecg(N), 1) 26 | wvd = WignerVilleDistribution(sig) 27 | wvd.run() 28 | wvd.plot(kind='contour', show_tf=True, sqmod=True) 29 | -------------------------------------------------------------------------------- /doc/_gallery/plot_5_4_2_wv_simultaneous_chirp.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | ==================================================== 11 | Wigner Ville Distribution of Two Simultaneous Chirps 12 | ==================================================== 13 | 14 | The signal to be analyzed contains two linear frequency modulations, each with 15 | a different slope. Note the interference between them. 16 | 17 | Figure 5.5 from the tutorial. 18 | """ 19 | 20 | from tftb.generators import fmlin, sigmerge 21 | from tftb.processing.cohen import WignerVilleDistribution 22 | 23 | N = 64 24 | sig = sigmerge(fmlin(N, 0, 0.4)[0], fmlin(N, 0.3, 0.5)[0], 1) 25 | tfr = WignerVilleDistribution(sig) 26 | tfr.run() 27 | tfr.plot(kind='contour', sqmod=True, show_tf=True) 28 | -------------------------------------------------------------------------------- /doc/_templates/localtoc.html: -------------------------------------------------------------------------------- 1 | {% if pagename != 'index' %} 2 | 3 | {%- if display_toc %} 4 | 5 | 8 | {%- endif %} 9 | 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /doc/_templates/navigation.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | {%- if prev %} 8 | 9 | 14 | {%- endif %} 15 | {%- if next %} 16 | 17 | 22 | {%- endif %} 23 | -------------------------------------------------------------------------------- /doc/api.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | API Reference 3 | ============= 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | apiref/tftb 9 | apiref/modules 10 | -------------------------------------------------------------------------------- /doc/auto_gallery.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Gallery of PyTFTB Examples 3 | ========================== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | auto_examples/index 9 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/amplitude_modulated/amexpos_bilateral.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | from tftb.generators import amexpos 14 | import matplotlib.pyplot as plt 15 | 16 | x = amexpos(160) 17 | plt.plot(x) 18 | plt.grid() 19 | plt.title("Two sided exponential amplitude modulation") 20 | plt.show() 21 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/amplitude_modulated/amexpos_unilateral.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | from tftb.generators import amexpos 14 | import matplotlib.pyplot as plt 15 | 16 | x = amexpos(160, kind='unilateral') 17 | plt.plot(x) 18 | plt.grid() 19 | plt.title("One sided exponential amplitude modulation") 20 | plt.show() 21 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/amplitude_modulated/amgauss1.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | from tftb.generators import amgauss 14 | import matplotlib.pyplot as plt 15 | 16 | x = amgauss(160) 17 | plt.plot(x) 18 | plt.grid() 19 | plt.title('Gaussian Amplitude Modulation') 20 | plt.show() 21 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/amplitude_modulated/amgauss2.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | from tftb.generators import amgauss 14 | import matplotlib.pyplot as plt 15 | 16 | x = amgauss(160, 90) 17 | plt.plot(x) 18 | plt.grid() 19 | plt.title('Gaussian Amplitude Modulation') 20 | plt.show() 21 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/amplitude_modulated/amgauss3.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | from tftb.generators import amgauss 14 | import matplotlib.pyplot as plt 15 | 16 | x = amgauss(160, 90, 40.0) 17 | plt.plot(x) 18 | plt.grid() 19 | plt.title('Gaussian Amplitude Modulation') 20 | plt.show() 21 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/amplitude_modulated/amrect1.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | from tftb.generators import amrect 14 | import matplotlib.pyplot as plt 15 | 16 | x = amrect(160, 90, 40.0) 17 | plt.plot(x) 18 | plt.grid() 19 | plt.title('Rectangular Amplitude Modulation.') 20 | plt.show() 21 | 22 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/amplitude_modulated/amtriang1.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | 14 | from tftb.generators import amtriang 15 | import matplotlib.pyplot as plt 16 | 17 | x = amtriang(160) 18 | plt.plot(x) 19 | plt.title('Triangular Amplitude Modulation') 20 | plt.grid() 21 | plt.show() 22 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/analytic_signals/anaask.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | 14 | from tftb.generators import anaask 15 | import matplotlib.pyplot as plt 16 | import numpy as np 17 | 18 | x, am = anaask(512, 64, 0.05) 19 | plt.subplot(211), plt.plot(np.real(x)) 20 | plt.xlim(0, 512) 21 | plt.grid() 22 | plt.title('Analytic ASK signal') 23 | plt.subplot(212), plt.plot(am) 24 | plt.xlim(0, 512) 25 | plt.grid() 26 | plt.title('Amplitude Modulation') 27 | plt.show() 28 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/analytic_signals/anabpsk.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | 14 | from tftb.generators import anabpsk 15 | import matplotlib.pyplot as plt 16 | import numpy as np 17 | 18 | x, am = anabpsk(300, 30, 0.1) 19 | plt.subplot(211), plt.plot(np.real(x)) 20 | plt.grid() 21 | plt.title('Analytic BPSK signal') 22 | plt.subplot(212), plt.plot(am) 23 | plt.grid() 24 | plt.title('Amplitude Modulation') 25 | plt.show() 26 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/analytic_signals/anafsk.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | 14 | from tftb.generators import anafsk 15 | import matplotlib.pyplot as plt 16 | import numpy as np 17 | 18 | x, am = anafsk(512, 54.0, 5.0) 19 | plt.subplot(211), plt.plot(np.real(x)) 20 | plt.xlim(0, 512) 21 | plt.grid() 22 | plt.title('Analytic FSK signal') 23 | plt.subplot(212), plt.plot(am) 24 | plt.xlim(0, 512) 25 | plt.grid() 26 | plt.title('Amplitude Modulation') 27 | plt.show() 28 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/analytic_signals/anapulse.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | 14 | from tftb.generators import anapulse 15 | import matplotlib.pyplot as plt 16 | import numpy as np 17 | 18 | x = 2.5 * anapulse(512, 301) 19 | plt.plot(np.real(x)) 20 | plt.xlim(0, 512) 21 | plt.grid() 22 | plt.title('Analytic Dirac Impulse') 23 | plt.show() 24 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/analytic_signals/anaqpsk.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | 14 | from tftb.generators import anaqpsk 15 | import matplotlib.pyplot as plt 16 | import numpy as np 17 | 18 | x, am = anaqpsk(512, 64.0, 0.05) 19 | plt.subplot(211), plt.plot(np.real(x)) 20 | plt.xlim(0, 512) 21 | plt.grid() 22 | plt.title('Analytic QPSK signal') 23 | plt.subplot(212), plt.plot(am) 24 | plt.xlim(0, 512) 25 | plt.grid() 26 | plt.title('Phase') 27 | plt.show() 28 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/analytic_signals/anasing.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | 14 | from tftb.generators import anasing 15 | import matplotlib.pyplot as plt 16 | import numpy as np 17 | 18 | x = anasing(128) 19 | plt.plot(np.real(x)) 20 | plt.xlim(0, 128) 21 | plt.grid() 22 | plt.title('Lipschitz Singularity') 23 | plt.show() 24 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/analytic_signals/anastep.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | 14 | from tftb.generators import anastep 15 | import matplotlib.pyplot as plt 16 | import numpy as np 17 | 18 | x = anastep(256, 128) 19 | plt.plot(np.real(x)) 20 | plt.xlim(0, 256) 21 | plt.grid() 22 | plt.title('Analytic Step Signal') 23 | plt.show() 24 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/frequency_modulated/fmconst.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | from tftb.generators import amgauss, fmconst 14 | import numpy as np 15 | import matplotlib.pyplot as plt 16 | 17 | z = amgauss(128, 50.0, 30.0) * fmconst(128, 0.05, 50)[0] 18 | plt.plot(np.real(z)) 19 | plt.xlim(0, 128) 20 | plt.grid() 21 | plt.title('Constant Frequency Modulation') 22 | plt.show() 23 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/frequency_modulated/fmhyp.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | """ 9 | 10 | """ 11 | 12 | from tftb.generators import fmhyp 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | 16 | signal, iflaw = fmhyp(128, (1, 0.5), (32, 0.1)) 17 | plt.subplot(211), plt.plot(np.real(signal)) 18 | plt.xlim(0, 128) 19 | plt.grid() 20 | plt.title('Hyperbolic Frequency Modulation') 21 | plt.subplot(212), plt.plot(iflaw) 22 | plt.xlim(0, 128) 23 | plt.grid() 24 | plt.title('Instantaneous Frequency') 25 | plt.show() 26 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/frequency_modulated/fmlin.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | from tftb.generators import amgauss, fmlin 14 | import numpy as np 15 | import matplotlib.pyplot as plt 16 | 17 | z = amgauss(128, 50.0, 30.0) * fmlin(128, 0.05, 0.3, 50)[0] 18 | plt.plot(np.real(z)) 19 | plt.xlim(0, 128) 20 | plt.grid() 21 | plt.title('Linear Frequency Modulation') 22 | plt.show() 23 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/frequency_modulated/fmodany.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | """ 9 | 10 | """ 11 | 12 | from tftb.generators import fmlin, fmodany, fmsin 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | 16 | y1, ifl1 = fmlin(100) 17 | y2, ifl2 = fmsin(100) 18 | iflaw = np.append(ifl1, ifl2) 19 | sig = fmodany(iflaw) 20 | 21 | plt.subplot(211), plt.plot(np.real(sig)) 22 | plt.grid() 23 | plt.title('Linear and Sinusoidal modulated signal') 24 | plt.subplot(212), plt.plot(iflaw) 25 | plt.grid() 26 | plt.title('Instantaneous frequency') 27 | plt.show() 28 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/frequency_modulated/fmpar.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | from tftb.generators import fmpar 14 | import numpy as np 15 | import matplotlib.pyplot as plt 16 | 17 | z, iflaw = fmpar(128, (0.4, -0.0112, 8.6806e-05)) 18 | plt.subplot(211), plt.plot(np.real(z)) 19 | plt.xlim(0, 128) 20 | plt.grid() 21 | plt.title('Parabolic Frequency Modulation') 22 | plt.subplot(212), plt.plot(iflaw) 23 | plt.xlim(0, 128) 24 | plt.grid() 25 | plt.title('Instantaneous Frequency') 26 | plt.show() 27 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/frequency_modulated/fmpower.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | from tftb.generators import fmpower 14 | import numpy as np 15 | import matplotlib.pyplot as plt 16 | 17 | z, iflaw = fmpower(128, 0.5, (1, 0.5, 100, 0.1)) 18 | plt.subplot(211), plt.plot(np.real(z)) 19 | plt.xlim(0, 128) 20 | plt.grid() 21 | plt.title('Power Law Modulation') 22 | plt.subplot(212), plt.plot(iflaw) 23 | plt.xlim(0, 128) 24 | plt.grid() 25 | plt.title('Instantaneous Frequency') 26 | plt.show() 27 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/frequency_modulated/fmsin.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | from tftb.generators import fmsin 14 | import numpy as np 15 | import matplotlib.pyplot as plt 16 | 17 | z = fmsin(140, period=100, t0=20.0, fnorm0=0.3, pm1=-1)[0] 18 | plt.plot(np.real(z)) 19 | plt.grid() 20 | plt.title('Sinusoidal Frequency Modulation') 21 | plt.show() 22 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/misc/altes.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | from tftb.generators.misc import altes 14 | import matplotlib.pyplot as plt 15 | 16 | x = altes(128, 0.1, 0.45) 17 | plt.plot(x) 18 | plt.xlim(0, 128) 19 | plt.grid() 20 | plt.title("Altes signal") 21 | plt.show() 22 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/misc/atoms.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | 14 | import numpy as np 15 | import matplotlib.pyplot as plt 16 | from tftb.generators.misc import atoms 17 | 18 | coordinates = np.array([[32.0, 0.3, 32.0, 1.0], 19 | [56.0, 0.15, 48.0, 1.22]]) 20 | sig = atoms(128, coordinates) 21 | plt.plot(np.real(sig)) 22 | plt.grid() 23 | plt.xlim(xmax=128) 24 | plt.title('Gaussian Atoms') 25 | plt.show() 26 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/misc/doppler.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | 14 | from tftb.generators import doppler 15 | import numpy as np 16 | import matplotlib.pyplot as plt 17 | 18 | fm, am, iflaw = doppler(512, 200.0, 65.0, 10.0, 50.0) 19 | plt.subplot(211), plt.plot(np.real(am * fm)) 20 | plt.title('Doppler') 21 | plt.grid() 22 | plt.xlim(0, 512) 23 | plt.subplot(212), plt.plot(iflaw) 24 | plt.title('Instantaneous Freqeuncy') 25 | plt.grid() 26 | plt.xlim(0, 512) 27 | 28 | plt.show() 29 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/misc/klauder.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | 14 | import matplotlib.pyplot as plt 15 | from tftb.generators import klauder 16 | 17 | x = klauder(128) 18 | plt.plot(x) 19 | plt.xlim(0, 128) 20 | plt.grid() 21 | plt.title('Klauder Wavelet') 22 | plt.show() 23 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/misc/mexhat.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | 14 | from tftb.generators import mexhat 15 | import matplotlib.pyplot as plt 16 | 17 | plt.plot(mexhat()) 18 | plt.grid() 19 | plt.title('Mexican Hat Wavelet') 20 | plt.show() 21 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/noise/dopnoise.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | from tftb.generators import dopnoise 16 | from tftb.processing.freq_domain import inst_freq 17 | 18 | z, iflaw = dopnoise(500, 200.0, 60.0, 10.0, 70.0, 128.0) 19 | plt.subplot(211), plt.plot(np.real(z)) 20 | plt.grid() 21 | plt.title('Complex noisy Doppler signal') 22 | plt.xlim(0, 500) 23 | ifl, t = inst_freq(z, np.arange(11, 479), 10) 24 | plt.subplot(212) 25 | plt.plot(iflaw, 'r', label='actual') 26 | plt.plot(t, ifl, 'g', label='estimated') 27 | plt.xlim(0, 500) 28 | plt.legend() 29 | plt.grid() 30 | plt.title('Instantaneous frequency') 31 | plt.show() 32 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/noise/noisecg.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | from tftb.generators import noisecg 16 | 17 | noise = noisecg(512) 18 | print(noise.mean()) 19 | print(noise.std() ** 2) 20 | plt.subplot(211), plt.plot(np.real(noise)) 21 | plt.xlim(0, 512) 22 | plt.grid() 23 | plt.title('Analytic complex Gaussian noise.') 24 | plt.subplot(212), plt.plot(np.linspace(-0.5, 0.5, 512), 25 | abs(np.fft.fftshift(np.fft.fft(noise))) ** 2) 26 | plt.grid() 27 | plt.title('Energy spectrum') 28 | plt.xlim(-0.5, 0.5) 29 | plt.show() 30 | -------------------------------------------------------------------------------- /doc/docstring_plots/generators/noise/noisecu.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | 14 | from tftb.generators import noisecu 15 | import numpy as np 16 | import matplotlib.pyplot as plt 17 | 18 | noise = noisecu(512) 19 | print(np.real(noise.mean())) 20 | print(noise.std() ** 2) 21 | 22 | plt.subplot(211), plt.plot(np.real(noise)) 23 | plt.title('Analytic complex white noise') 24 | plt.xlim(0, 512) 25 | plt.grid() 26 | plt.subplot(212), plt.plot(np.linspace(-0.5, 0.5, 512), 27 | np.abs(np.fft.fftshift(np.fft.fft(noise))) ** 2) 28 | plt.title('Energy spectrum') 29 | plt.xlabel('Normalized Frequency') 30 | plt.xlim(-0.5, 0.5) 31 | plt.grid() 32 | plt.show() 33 | -------------------------------------------------------------------------------- /doc/docstring_plots/processing/freq_domain/group_delay.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | from tftb.generators import amgauss, fmlin 16 | from tftb.processing import group_delay 17 | 18 | x = amgauss(128, 64.0, 30) * fmlin(128, 0.1, 0.4)[0] 19 | fnorm = np.arange(0.1, 0.38, step=0.04) 20 | gd = group_delay(x, fnorm) 21 | plt.plot(gd, fnorm) 22 | plt.xlim(0, 128) 23 | plt.grid() 24 | plt.title('Group delay estimation of linear chirp') 25 | plt.xlabel('Group delay') 26 | plt.ylabel('Normalized frequency') 27 | plt.show() 28 | -------------------------------------------------------------------------------- /doc/docstring_plots/processing/freq_domain/inst_freq.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | 14 | import matplotlib.pyplot as plt 15 | from tftb.processing import inst_freq 16 | from tftb.generators import fmsin 17 | 18 | x = fmsin(70, 0.05, 0.35, 25)[0] 19 | instf, timestamps = inst_freq(x) 20 | plt.plot(timestamps, instf) 21 | plt.xlim(0, 70) 22 | plt.grid() 23 | plt.title("Instantaneous frequency estimation") 24 | plt.xlabel('Time') 25 | plt.ylabel('Frequency') 26 | plt.show() 27 | -------------------------------------------------------------------------------- /doc/docstring_plots/processing/stft.py: -------------------------------------------------------------------------------- 1 | from tftb.processing.linear import ShortTimeFourierTransform 2 | from tftb.generators import fmconst 3 | import numpy as np 4 | sig = np.r_[fmconst(128, 0.2)[0], fmconst(128, 0.4)[0]] 5 | tfr = ShortTimeFourierTransform(sig) 6 | tfr.run() 7 | tfr.plot() 8 | -------------------------------------------------------------------------------- /doc/docstring_plots/processing/utils/derive_window.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | import matplotlib.pyplot as plt 14 | from scipy.signal import hann 15 | from tftb.processing.utils import derive_window 16 | 17 | window = hann(210) 18 | plt.subplot(211), plt.plot(window) 19 | plt.xlim(0, 210) 20 | plt.grid() 21 | plt.title('Hanning window') 22 | plt.subplot(212), plt.plot(derive_window(window)) 23 | plt.xlim(0, 210) 24 | plt.grid() 25 | plt.title('Approximate derivative') 26 | plt.show() 27 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. pytftb documentation master file, created by 2 | sphinx-quickstart on Tue Jun 9 21:32:46 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to pytftb's documentation! 7 | ================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | introduction 15 | nonstationary_signals 16 | auto_gallery 17 | api 18 | 19 | 20 | 21 | Indices and tables 22 | ================== 23 | 24 | * :ref:`genindex` 25 | * :ref:`modindex` 26 | * :ref:`search` 27 | 28 | -------------------------------------------------------------------------------- /doc/introduction.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Introduction to PyTFTB 3 | ====================== 4 | 5 | About PyTFTB 6 | ------------ 7 | 8 | The PyTFTB project began as a Python implementation of the 9 | `TFTB toolbox `_ developed by François Auger, 10 | Olivier Lemoine, Paulo Gonçalvès and Patrick Flandrin. While the Python 11 | implementation (henceforth referred to as PyTFTB) and the MATLAB implementation 12 | (henceforth referred to as TFTB) are similar in the core algorithms and the 13 | basic code organization, the very nature of the Python programming language 14 | has motivated a very different approach in architecture of PyTFTB (differences 15 | between the two packages have been discussed in detail in the next section). 16 | Thus, someone who is familiar with TFTB should find the PyTFTB API comfortably 17 | within grasp, and someone who is beginning with PyTFTB should find it a fully 18 | self contained library to use. 19 | 20 | Comparison of TFTB and PyTFTB 21 | ----------------------------- 22 | 23 | TFTB is broadly a collection of MATLAB functions and demos that demonstrate the use of these functions. 24 | A detailed reference of these functions can be found `here `_. The fact that 25 | Python is a general purpose programming language affords the users and the developers a lot of freedom, especially with 26 | regard to code reuse and interfacing. The important differences in implementation are as follows: 27 | 28 | 1. PyTFTB makes heavy use of Python's object oriented design. This allows for code reuse and interfacing. Algorithms 29 | that are very closely related to each other can inherit from thew same base class and reuse each others methods. 30 | 31 | 2. In TFTB, visualization of time frequency distributions is handled by dedicated functions like `tfrview` and `tfrqview` 32 | whereas in PyTFTB, they are tightly coupled to the specific representation being computed. 33 | 34 | 3. PyTFTB is heavily dependent on the SciPy stack - especially the NumPy and the SciPy libraries. Whichever piece of 35 | code can be delegated to these libraries is delegated to them. 36 | 37 | Quick Start 38 | ----------- 39 | 40 | .. toctree:: 41 | :maxdepth: 2 42 | 43 | quickstart/intro_examples_1.rst 44 | quickstart/intro_examples_2.rst 45 | 46 | -------------------------------------------------------------------------------- /doc/misc_plots/nonstationary_phase_plot.py: -------------------------------------------------------------------------------- 1 | from tftb.generators import fmlin, amgauss 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | y_nonstat, _ = fmlin(2048) # Already analytic, no need of Hilbert transorm 6 | y_nonstat *= amgauss(2048) 7 | plt.plot(np.real(y_nonstat), np.imag(y_nonstat)) 8 | plt.xlabel("Real part") 9 | plt.ylabel("Imaginary part") 10 | plt.show() 11 | -------------------------------------------------------------------------------- /doc/misc_plots/stationary_phase_plot.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from scipy.signal import hilbert 4 | 5 | fs = 32768 6 | ts = np.linspace(0, 1, fs) 7 | y1 = np.sin(2 * np.pi * 697 * ts) 8 | y2 = np.sin(2 * np.pi * 1336 * ts) 9 | y = (y1 + y2) / 2 10 | 11 | 12 | y = y[:int(fs / 16)] 13 | y_analytic = hilbert(y) 14 | plt.plot(np.real(y_analytic), np.imag(y_analytic)) 15 | plt.xlabel("Real part") 16 | plt.ylabel("Imaginary part") 17 | plt.show() 18 | -------------------------------------------------------------------------------- /doc/misc_plots/touchtone.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Cube26 product code 6 | # 7 | # (C) Copyright 2015 Cube26 Software Pvt Ltd 8 | # All right reserved. 9 | # 10 | # This file is confidential and NOT open source. Do not distribute. 11 | # 12 | 13 | """ 14 | 15 | """ 16 | import numpy as np 17 | import matplotlib.pyplot as plt 18 | fs = 32768 19 | ts = np.linspace(0, 1, fs) 20 | y1 = np.sin(2 * np.pi * 697 * ts) 21 | y2 = np.sin(2 * np.pi * 1336 * ts) 22 | y = (y1 + y2) / 2 23 | plt.plot(ts, y) 24 | plt.xlim(0, 0.1) 25 | plt.show() 26 | -------------------------------------------------------------------------------- /doc/misc_plots/touchtone_mean_convolve.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Cube26 product code 6 | # 7 | # (C) Copyright 2015 Cube26 Software Pvt Ltd 8 | # All right reserved. 9 | # 10 | # This file is confidential and NOT open source. Do not distribute. 11 | # 12 | 13 | """ 14 | 15 | """ 16 | import numpy as np 17 | import matplotlib.pyplot as plt 18 | fs = 32768 19 | ts = np.linspace(0, 1, fs) 20 | y1 = np.sin(2 * np.pi * 697 * ts) 21 | y2 = np.sin(2 * np.pi * 1336 * ts) 22 | y = (y1 + y2) / 2 23 | 24 | Y = np.fft.fft(y) 25 | freqs = np.arange(fs) 26 | plt.plot(freqs, np.abs(Y)) 27 | plt.show() 28 | -------------------------------------------------------------------------------- /doc/misc_plots/uncertainty_example_plot.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | f1, f2 = 500, 1000 4 | t1, t2 = 0.192, 0.196 5 | f_sample = 8000 6 | n_points = 2048 7 | ts = np.arange(n_points, dtype=float) / f_sample 8 | signal = np.sin(2 * np.pi * f1 * ts) + np.sin(2 * np.pi * f2 * ts) 9 | signal[int(t1 * f_sample) - 1] += 3 10 | signal[int(t2 * f_sample) - 1] += 3 11 | plt.plot(ts, signal) 12 | plt.show() 13 | -------------------------------------------------------------------------------- /doc/misc_plots/uncertainty_stft.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from tftb.processing import ShortTimeFourierTransform 4 | f1, f2 = 500, 1000 5 | t1, t2 = 0.192, 0.196 6 | f_sample = 8000 7 | n_points = 2048 8 | ts = np.arange(n_points, dtype=float) / f_sample 9 | signal = np.sin(2 * np.pi * f1 * ts) + np.sin(2 * np.pi * f2 * ts) 10 | signal[int(t1 * f_sample) - 1] += 3 11 | signal[int(t2 * f_sample) - 1] += 3 12 | 13 | wlengths = [2, 4, 8, 16] 14 | nf = [(w * 0.001 * f_sample) + 1 for w in wlengths] 15 | fig = plt.figure() 16 | extent = [0, ts.max(), 0, 2000] 17 | for i, wlen in enumerate(wlengths): 18 | window = np.ones((int(nf[i]),), dtype=float) 19 | stft = ShortTimeFourierTransform(signal, fwindow=window) 20 | stft.run() 21 | ax = fig.add_subplot(4, 1, i + 1) 22 | stft.plot(ax=ax, default_annotation=False, show=False, 23 | extent=extent) 24 | ax.set_yticklabels([]) 25 | if i != 3: 26 | ax.set_xticklabels([]) 27 | ax.set_title("window duration = {} ms".format(wlen)) 28 | plt.subplots_adjust(hspace=0.5) 29 | plt.show() 30 | -------------------------------------------------------------------------------- /doc/quickstart/intro_examples_1.rst: -------------------------------------------------------------------------------- 1 | ====================================== 2 | Example 1: Linear Frequency Modulation 3 | ====================================== 4 | 5 | Let us consider first a signal with constant amplitude, and with a linear frequency modulation - i.e. a signal such that 6 | its amplitude remains constant, but frequency increases linearly with time - varying from 0 to 0.5 in 7 | normalized frequency (ratio of the frequency in Hertz to the sampling frequency, with respect to the Shannon sampling 8 | theorem). This signal is called a chirp, and as its frequency content is varying with time, it is a non-stationary 9 | signal. To obtain such a signal, we can use the function ``tftb.generators.fmlin``, which generates a linear frequency 10 | modulation. 11 | 12 | >>> from tftb.generators import amgauss, fmlin 13 | >>> import matplotlib.pyplot as plt 14 | >>> import numpy as np 15 | >>> z = amgauss(128, 50, 40) * fmlin(128, 0.05, 0.3, 50)[0] 16 | >>> plt.plot(np.real(z)) 17 | >>> plt.title("Linear Frequency Modulation") 18 | >>> plt.show() 19 | 20 | .. plot:: docstring_plots/generators/frequency_modulated/fmlin.py 21 | 22 | From this time-domain representation, it is difficult to say what kind of modulation is contained in this signal: 23 | what are the initial and final frequencies, is it a linear, parabolic, hyperbolic... frequency modulation? 24 | 25 | If we now consider the energy spectrum of the signal ``z`` by squaring the modulus of its Fourier transform 26 | (using the ``numpy.fft.fft`` function): 27 | 28 | >>> import numpy as np 29 | >>> dsp1 = np.fft.fftshift(np.abs(np.fft.fft(z)) ** 2) 30 | >>> plt.plot(np.arange(-64, 64, dtype=float) / 128.0, dsp1) 31 | >>> plt.xlim(-0.5, 0.5) 32 | >>> plt.title('Spectrum') 33 | >>> plt.ylabel('Squared modulus') 34 | >>> plt.xlabel('Normalized Frequency') 35 | >>> plt.grid() 36 | >>> plt.show() 37 | 38 | .. plot:: _gallery/plot_1_3_1_chirp_spectrum.py 39 | 40 | we still can not say, from this plot, anything about the evolution in time of the frequency content. This is due to the 41 | fact that the Fourier transform is a decomposition on complex exponentials, which are of infinite duration and 42 | completely unlocalized in time. Time information is in fact encoded in the phase of the Fourier transform 43 | (which is simply ignored by the energy spectrum), but their interpretation is not straightforward and their direct 44 | extraction is faced with a number of difficulties such as phase unwrapping. In order to have a more informative 45 | description of such signals, it would be better to directly represent their frequency content while still keeping the 46 | time description parameter. This is precisely the aim of time-frequency analysis. To illustrate this, let us try the 47 | Wigner-Ville distribution on this signal. 48 | 49 | >>> from tftb.processing import WignerVilleDistribution 50 | >>> wvd = WignerVilleDistribution(z) 51 | >>> wvd.run() 52 | >>> wvd.plot(kind='contour', extent=[0, n_points, fmin, fmax]) 53 | 54 | .. plot:: _gallery/plot_1_3_1_chirp_wv.py 55 | 56 | we can see that the linear progression of the frequency with time, from 0 to 0.5, is clearly shown. 57 | 58 | If we now add some complex white gaussian noise on this signal, 59 | 60 | >>> from tftb.generators import sigmerge, noisecg 61 | >>> noisy_signal = sigmerge(z, noisecg(128), 0) 62 | >>> plt.plot(np.real(noisy_signal)) 63 | >>> plt.xlim(0, 128) 64 | >>> plt.title('Noisy chirp') 65 | >>> plt.ylabel('Real Part') 66 | >>> plt.xlabel('Time') 67 | >>> plt.grid() 68 | >>> plt.show() 69 | 70 | .. plot:: _gallery/plot_1_3_1_noisy_chirp.py 71 | 72 | and consider the spectrum of it, 73 | 74 | >>> dsp1 = np.fft.fftshift(np.abs(np.fft.fft(noisy_signal)) ** 2) 75 | >>> plt.plot(np.arange(-64, 64, dtype=float) / 128.0, dsp1) 76 | >>> plt.xlim(-0.5, 0.5) 77 | >>> plt.title('Spectrum of Noisy Chirp') 78 | >>> plt.ylabel('Squared modulus') 79 | >>> plt.xlabel('Normalized Frequency') 80 | >>> plt.grid() 81 | >>> plt.show() 82 | 83 | .. plot:: _gallery/plot_1_3_1_noisy_chirp_spectrum.py 84 | 85 | it is worse than before to interpret these plots. On the other hand, the Wigner-Ville distribution still show quite 86 | clearly the linear progression of the frequency with time. 87 | 88 | >>> wvd = WignerVilleDistribution(noisy_signal) 89 | >>> wvd.run() 90 | >>> wvd.plot(kind='contour') 91 | 92 | .. plot:: _gallery/plot_1_3_1_noisy_chirp_wv.py 93 | -------------------------------------------------------------------------------- /doc/quickstart/intro_examples_2.rst: -------------------------------------------------------------------------------- 1 | ================================= 2 | Example 2: Noisy Transient Signal 3 | ================================= 4 | 5 | The second introductory example is a transient signal embedded in a -5 dB white gaussian noise. This transient signal 6 | is a constant frequency modulated by a one-sided exponential amplitude. The signal and its spectrum are generated as 7 | follows: 8 | 9 | >>> import numpy as np 10 | >>> import matplotlib.pyplot as plt 11 | >>> from tftb.generators import amexpos, fmconst, sigmerge, noisecg 12 | >>> 13 | >>> # Generate a noisy transient signal. 14 | >>> transsig = amexpos(64, kind='unilateral') * fmconst(64)[0] 15 | >>> signal = np.hstack((np.zeros((100,)), transsig, np.zeros((92,)))) 16 | >>> signal = sigmerge(signal, noisecg(256), -5) 17 | >>> fig, ax = plt.subplots(2, 1) 18 | >>> ax1, ax2 = ax 19 | >>> ax1.plot(np.real(signal)) 20 | >>> ax1.grid() 21 | >>> ax1.set_title('Noisy Transient Signal') 22 | >>> ax1.set_xlabel('Time') 23 | >>> ax1.set_xlim((0, 256)) 24 | >>> ax1.set_ylim((np.real(signal).max(), np.real(signal.min()))) 25 | >>> 26 | >>> # Energy spectrum of the signal 27 | >>> dsp = np.fft.fftshift(np.abs(np.fft.fft(signal)) ** 2) 28 | >>> ax2.plot(np.arange(-128, 128, dtype=float) / 256, dsp) 29 | >>> ax2.set_title('Energy spectrum of noisy transient signal') 30 | >>> ax2.set_xlabel('Normalized frequency') 31 | >>> ax2.grid() 32 | >>> ax2.set_xlim(-0.5, 0.5) 33 | >>> 34 | >>> plt.subplots_adjust(hspace=0.5) 35 | >>> 36 | >>> plt.show() 37 | 38 | .. plot:: _gallery/plot_1_3_3_transient.py 39 | 40 | From these representations, it is difficult to localize precisely the signal in the time-domain as well as in the 41 | frequency domain. Now let us have a look at the spectrogram of this signal: 42 | 43 | >>> from scipy.signal import hamming 44 | >>> from tftb.processing import Spectrogram 45 | >>> fwindow = hamming(65) 46 | >>> spec = Spectrogram(signal, n_fbins=128, fwindow=fwindow) 47 | >>> spec.run() 48 | >>> spec.plot(kind="contour", threshold=0.1, show_tf=False) 49 | 50 | .. plot:: _gallery/plot_1_3_3_transient_spectrogram.py 51 | 52 | the transient signal appears distinctly around the normalized frequency 0.25, and between time points 125 and 160. 53 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx-gallery 2 | scikit-image 3 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "tftb" 3 | version = "0.2.0" 4 | description = "A Python library for time-frquency analysis and visualization." 5 | authors = [ 6 | {name = "deshpande.jaidev@gmail.com",email = "deshpande.jaidev@gmail.com"} 7 | ] 8 | license = {text = "MIT"} 9 | readme = "README.md" 10 | requires-python = ">=3.9" 11 | dependencies = [ 12 | "numpy (>=1.22.4,<2.0.0)", 13 | "scipy (>=1.9.0,<2.0.0)", 14 | "matplotlib (>=3.6.0,<4.0.0)" 15 | ] 16 | 17 | 18 | [build-system] 19 | requires = ["poetry-core>=2.0.0,<3.0.0"] 20 | build-backend = "poetry.core.masonry.api" 21 | 22 | [tool.poetry.group.test.dependencies] 23 | pytest = "^8.3.4" 24 | scikit-image = "^0.24.0" 25 | 26 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length=99 3 | ignore=W503,E731 4 | exclude=doc/* 5 | -------------------------------------------------------------------------------- /tftb/__init__.py: -------------------------------------------------------------------------------- 1 | from tftb import generators, processing, utils 2 | __all__ = ["generators", "processing", "utils"] 3 | -------------------------------------------------------------------------------- /tftb/generators/__init__.py: -------------------------------------------------------------------------------- 1 | from .amplitude_modulated import amexpos, amgauss, amrect, amtriang 2 | from .frequency_modulated import (fmconst, fmhyp, fmlin, fmodany, fmpar, 3 | fmpower, fmsin) 4 | from .utils import sigmerge, scale 5 | from .noise import dopnoise, noisecg, noisecu 6 | from .analytic_signals import (anaask, anabpsk, anafsk, anapulse, anaqpsk, 7 | anasing, anastep) 8 | from .misc import doppler, gdpower, klauder, mexhat, altes, atoms 9 | 10 | __all__ = ['amexpos', 'amgauss', 'amrect', 'amtriang', 'fmconst', 'fmhyp', 11 | 'fmlin', 'fmodany', 'fmpar', 'fmpower', 'fmsin', 'sigmerge', 12 | 'scale', 'dopnoise', 'noisecg', 'noisecu', 'anaask', 'anabpsk', 13 | 'anafsk', 'anapulse', 'anaqpsk', 'anasing', 'anastep', 14 | 'doppler', 'gdpower', 'klauder', 'mexhat', 'altes', 'atoms'] 15 | -------------------------------------------------------------------------------- /tftb/generators/amplitude_modulated.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from math import sqrt 3 | 4 | 5 | def amgauss(n_points, t0=None, spread=None): 6 | """Generate a Gaussian amplitude modulated signal. 7 | 8 | :param n_points: Number of points in the output. 9 | :param t0: Center of the Gaussian function. (default: t0 / 2) 10 | :param spread: Standard deviation of the Gaussian. (default 2 * 11 | sqrt(n_points) 12 | :type n_points: int 13 | :type t0: float 14 | :type spread: float 15 | :return: Gaussian function centered at time ``t0``. 16 | :rtype: numpy.ndarray 17 | :Example: 18 | >>> x = amgauss(160) 19 | >>> plot(x) #doctest: +SKIP 20 | 21 | .. plot:: docstring_plots/generators/amplitude_modulated/amgauss1.py 22 | >>> x = amgauss(160, 90) 23 | >>> plot(x) #doctest: +SKIP 24 | 25 | .. plot:: docstring_plots/generators/amplitude_modulated/amgauss2.py 26 | """ 27 | if t0 is None: 28 | t0 = round(n_points / 2.0) 29 | 30 | if spread is None: 31 | spread = 2 * sqrt(n_points) 32 | 33 | if n_points <= 0: 34 | raise TypeError("n_points should be >= 0") 35 | else: 36 | tmt0 = np.arange(1, n_points + 1, dtype=float) - t0 37 | y = np.exp(-((tmt0 / spread) ** 2) * np.pi) 38 | return y 39 | 40 | 41 | def amexpos(n_points, t0=None, spread=None, kind="bilateral"): 42 | """Exponential amplitude modulation. 43 | 44 | `amexpos` generates an exponential amplitude modulation starting at time 45 | `t0` and spread proportioanl to `spread`. 46 | 47 | :param n_points: Number of points. 48 | :param kind: "bilateral" (default) or "unilateral" 49 | :param t0: Time center. 50 | :param spread: Standard deviation. 51 | :type n_points: int 52 | :type kind: str 53 | :type t0: float 54 | :type spread: float 55 | :return: exponential function 56 | :rtype: numpy.ndarray 57 | :Examples: 58 | >>> x = amexpos(160) 59 | >>> plot(x) #doctest: +SKIP 60 | 61 | .. plot:: docstring_plots/generators/amplitude_modulated/amexpos_bilateral.py 62 | >>> x = amexpos(160, kind='unilateral') 63 | >>> plot(x) #doctest: +SKIP 64 | 65 | .. plot:: docstring_plots/generators/amplitude_modulated/amexpos_unilateral.py 66 | """ 67 | if t0 is None: 68 | t0 = round(n_points / 2.0) 69 | if spread is None: 70 | spread = 2 * sqrt(n_points) 71 | 72 | if n_points <= 0: 73 | raise TypeError 74 | else: 75 | tmt0 = np.arange(n_points) - t0 76 | if kind == "bilateral": 77 | y = np.exp(-sqrt(2 * np.pi) * np.abs(tmt0) / spread) 78 | else: 79 | y = np.exp(-sqrt(np.pi) * tmt0 / spread) * (tmt0 >= 0.0) 80 | return y 81 | 82 | 83 | def amrect(n_points, t0=None, spread=None): 84 | """Generate a rectangular amplitude modulation. 85 | 86 | :param n_points: Number of points in the function. 87 | :param t0: Time center 88 | :param spread: standard deviation of the function. 89 | :type n_points: int 90 | :type t0: float 91 | :type spread: float 92 | :return: A rectangular amplitude modulator. 93 | :rtype: numpy.ndarray. 94 | :Examples: 95 | >>> x = amrect(160, 90, 40.0) 96 | >>> plot(x) #doctest: +SKIP 97 | 98 | .. plot:: docstring_plots/generators/amplitude_modulated/amrect1.py 99 | """ 100 | if t0 is None: 101 | t0 = round(n_points / 2.0) 102 | if spread is None: 103 | spread = 2 * sqrt(n_points) 104 | 105 | if n_points <= 0: 106 | raise TypeError 107 | else: 108 | tmt0 = np.arange(n_points) - t0 109 | y = np.abs(tmt0) <= 0.5 * spread * sqrt(3.0 / np.pi) 110 | return y 111 | 112 | 113 | def amtriang(n_points, t0=None, spread=None): 114 | """Generate a triangular amplitude modulation. 115 | 116 | :param n_points: Number of points in the function. 117 | :param t0: Time center 118 | :param spread: standard deviation of the function. 119 | :type n_points: int 120 | :type t0: float 121 | :type spread: float 122 | :return: A triangular amplitude modulator. 123 | :rtype: numpy.ndarray. 124 | :Examples: 125 | >>> x = amtriang(160) 126 | >>> plot(x) #doctest: +SKIP 127 | 128 | .. plot:: docstring_plots/generators/amplitude_modulated/amtriang1.py 129 | """ 130 | if t0 is None: 131 | t0 = round(n_points / 2.0) 132 | if spread is None: 133 | spread = 2 * sqrt(n_points) 134 | 135 | if n_points <= 0: 136 | raise TypeError 137 | else: 138 | tmt0 = np.arange(n_points) - t0 139 | L = sqrt(10.0 / np.pi) * spread / 2.0 140 | t = np.amin(np.c_[L + tmt0, L - tmt0], axis=1) 141 | t = np.c_[t, np.zeros(t.shape)] 142 | y = np.amax(t, axis=1) / L 143 | 144 | return y 145 | 146 | 147 | if __name__ == '__main__': 148 | amgauss(128) 149 | amexpos(128) 150 | amrect(128) 151 | amtriang(128) 152 | -------------------------------------------------------------------------------- /tftb/generators/analytic_signals.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy import pi 3 | from scipy.signal import hilbert 4 | from tftb.generators import fmconst 5 | 6 | 7 | def anaask(n_points, n_comp=None, f0=0.25): 8 | """Generate an amplitude shift (ASK) keying signal. 9 | 10 | :param n_points: number of points. 11 | :param n_comp: number of points of each component. 12 | :param f0: normalized frequency. 13 | :type n_points: int 14 | :type n_comp: int 15 | :type f0: float 16 | :return: Tuple containing the modulated signal and the amplitude modulation. 17 | :rtype: tuple(numpy.ndarray) 18 | :Examples: 19 | >>> x, am = anaask(512, 64, 0.05) 20 | >>> subplot(211), plot(real(x)) #doctest: +SKIP 21 | >>> subplot(212), plot(am) #doctest: +SKIP 22 | 23 | .. plot:: docstring_plots/generators/analytic_signals/anaask.py 24 | """ 25 | if n_comp is None: 26 | n_comp = round(n_points / 2.0) 27 | if (f0 < 0) or (f0 > 0.5): 28 | raise TypeError("f0 must be between 0 and 0.5") 29 | m = int(np.ceil(n_points / n_comp)) 30 | jumps = np.random.rand(m) 31 | am = np.repeat(jumps, n_comp)[:n_points] 32 | fm, _ = fmconst(n_points, f0, 1) 33 | y = am * fm 34 | return y, am 35 | 36 | 37 | def anabpsk(n_points, n_comp=None, f0=0.25): 38 | """Binary phase shift keying (BPSK) signal. 39 | 40 | :param n_points: number of points. 41 | :param n_comp: number of points in each component. 42 | :param f0: normalized frequency. 43 | :type n_points: int 44 | :type n_comp: int 45 | :type f0: float 46 | :return: BPSK signal 47 | :rtype: numpy.ndarray 48 | :Examples: 49 | >>> x, am = anabpsk(300, 30, 0.1) 50 | >>> subplot(211), plot(real(x)) #doctest: +SKIP 51 | >>> subplot(212), plot(am) #doctest: +SKIP 52 | 53 | .. plot:: docstring_plots/generators/analytic_signals/anabpsk.py 54 | """ 55 | if n_comp is None: 56 | n_comp = round(n_points / 5.0) 57 | if (f0 < 0) or (f0 > 0.5): 58 | raise TypeError("f0 must be between 0 and 0.5") 59 | m = int(np.ceil(n_points / n_comp)) 60 | jumps = 2.0 * np.round(np.random.rand(m)) - 1 61 | am = np.repeat(jumps, n_comp)[:n_points] 62 | y = am * fmconst(n_points, f0, 1)[0] 63 | return y, am 64 | 65 | 66 | def anafsk(n_points, n_comp=None, Nbf=4): 67 | """Frequency shift keying (FSK) signal. 68 | 69 | :param n_points: number of points. 70 | :param n_comp: number of points in each components. 71 | :param Nbf: number of distinct frequencies. 72 | :type n_points: int 73 | :type n_comp: int 74 | :type Nbf: int 75 | :return: FSK signal. 76 | :rtype: numpy.ndarray 77 | :Examples: 78 | >>> x, am = anafsk(512, 54.0, 5.0) 79 | >>> subplot(211), plot(real(x)) #doctest: +SKIP 80 | >>> subplot(212), plot(am) #doctest: +SKIP 81 | 82 | .. plot:: docstring_plots/generators/analytic_signals/anafsk.py 83 | """ 84 | if n_comp is None: 85 | n_comp = int(round(n_points / 5.0)) 86 | m = np.ceil(n_points / n_comp) 87 | m = int(np.ceil(n_points / n_comp)) 88 | freqs = 0.25 + 0.25 * (np.floor(Nbf * np.random.rand(m, 1)) / Nbf - (Nbf - 1) / (2 * Nbf)) 89 | iflaw = np.repeat(freqs, n_comp).ravel() 90 | y = np.exp(1j * 2 * pi * np.cumsum(iflaw)) 91 | return y, iflaw 92 | 93 | 94 | def anapulse(n_points, ti=None): 95 | """Analytic projection of unit amplitude impulse signal. 96 | 97 | :param n_points: Number of points. 98 | :param ti: time position of the impulse. 99 | :type n_points: int 100 | :type ti: float 101 | :return: analytic impulse signal. 102 | :rtype: numpy.ndarray 103 | :Examples: 104 | >>> x = 2.5 * anapulse(512, 301) 105 | >>> plot(real(x)) #doctest: +SKIP 106 | 107 | .. plot:: docstring_plots/generators/analytic_signals/anapulse.py 108 | """ 109 | if ti is None: 110 | ti = np.round(n_points / 2) 111 | t = np.arange(n_points) 112 | x = t == ti 113 | y = hilbert(x.astype(float)) 114 | return y 115 | 116 | 117 | def anaqpsk(n_points, n_comp=None, f0=0.25): 118 | """Quaternary Phase Shift Keying (QPSK) signal. 119 | 120 | :param n_points: number of points. 121 | :param n_comp: number of points in each component. 122 | :param f0: normalized frequency 123 | :type n_points: int 124 | :type n_comp: int 125 | :type f0: float 126 | :return: complex phase modulated signal of normalized frequency f0 and 127 | initial phase. 128 | :rtype: tuple 129 | :Examples: 130 | >>> x, phase = anaqpsk(512, 64.0, 0.05) 131 | >>> subplot(211), plot(real(x)) #doctest: +SKIP 132 | >>> subplot(212), plot(phase) #doctest: +SKIP 133 | 134 | .. plot:: docstring_plots/generators/analytic_signals/anaqpsk.py 135 | """ 136 | if n_comp is None: 137 | n_comp = round(n_points / 5.0) 138 | if (f0 < 0) or (f0 > 0.5): 139 | raise TypeError("f0 must be between 0 and 0.5") 140 | m = int(np.ceil(n_points / n_comp)) 141 | jumps = np.floor(4 * np.random.rand(m)) 142 | jumps[jumps == 4] = 3 143 | pm0 = (np.pi * np.repeat(jumps, n_comp) / 2).ravel() 144 | tm = np.arange(n_points) - 1 145 | pm = 2 * np.pi * f0 * tm + pm0 146 | y = np.exp(1j * pm) 147 | return y, pm0 148 | 149 | 150 | def anasing(n_points, t0=None, h=0.0): 151 | """Lipschitz singularity. 152 | Refer to the wiki page on `Lipschitz condition`, good test case. 153 | 154 | :param n_points: number of points in time. 155 | :param t0: time localization of singularity 156 | :param h: strength of the singularity 157 | :type n_points: int 158 | :type t0: float 159 | :type h: float 160 | :return: N-point Lipschitz singularity centered around t0 161 | :rtype: numpy.ndarray 162 | :Examples: 163 | >>> x = anasing(128) 164 | >>> plot(real(x)) #doctest: +SKIP 165 | 166 | .. plot:: docstring_plots/generators/analytic_signals/anasing.py 167 | """ 168 | if t0 is None: 169 | t0 = n_points / 2.0 170 | if h <= 0: 171 | start, end = 1.0 / n_points, 0.5 - 1.0 / n_points 172 | N = int(end / start) 173 | f = np.linspace(start, end, N) 174 | y = np.zeros((int(n_points / 2.0),), dtype=complex) 175 | y[1:int(n_points / 2)] = (f ** (-1 - h)) * np.exp(-1j * 2 * pi * f * (t0 - 1)) 176 | x = np.real(np.fft.ifft(y, n_points)) 177 | x = x / x.max() 178 | x = x - np.sign(x.min()) * np.abs(x.min()) 179 | else: 180 | t = np.arange(n_points) 181 | x = np.abs(t - t0) ** h 182 | x = x.max() - x 183 | x = hilbert(x) 184 | return x 185 | 186 | 187 | def anastep(n_points, ti=None): 188 | """Analytic projection of unit step signal. 189 | 190 | :param n_points: Number of points. 191 | :param ti: starting position of unit step. 192 | :type n_points: int 193 | :type ti: float 194 | :return: output signal 195 | :rtype: numpy.ndarray 196 | :Examples: 197 | >>> x = anastep(256, 128) 198 | >>> plot(real(x)) #doctest: +SKIP 199 | 200 | .. plot:: docstring_plots/generators/analytic_signals/anastep.py 201 | """ 202 | if ti is None: 203 | ti = np.round(n_points / 2) 204 | t = np.arange(n_points) 205 | x = t > ti 206 | y = hilbert(x.astype(float)) 207 | return y 208 | 209 | 210 | if __name__ == '__main__': 211 | anaask(128) 212 | anabpsk(128, 128) 213 | anafsk(128) 214 | anapulse(128) 215 | anasing(128) 216 | anastep(128) 217 | -------------------------------------------------------------------------------- /tftb/generators/noise.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from tftb.utils import nextpow2 3 | from scipy.signal import hilbert 4 | 5 | 6 | def noisecu(n_points): 7 | """Compute analytic complex uniform white noise. 8 | 9 | :param n_points: Length of the noise signal. 10 | :type n_points: int 11 | :return: analytic complex uniform white noise signal of length N 12 | :rtype: numpy.ndarray 13 | :Examples: 14 | >>> import matplotlib.pyplot as plt 15 | >>> import numpy as np 16 | >>> noise = noisecu(512) 17 | >>> print("%.2f" % abs((noise ** 2).mean())) 18 | 0.00 19 | >>> print("%.1f" % np.std(noise) ** 2) 20 | 1.0 21 | >>> plt.subplot(211), plt.plot(real(noise)) #doctest: +SKIP 22 | >>> plt.subplot(212), #doctest: +SKIP 23 | >>> plt.plot(linspace(-0.5, 0.5, 512), abs(fftshift(fft(noise))) ** 2) #doctest: +SKIP 24 | 25 | .. plot:: docstring_plots/generators/noise/noisecu.py 26 | """ 27 | if n_points <= 2: 28 | noise = (np.random.rand(n_points, 1) - 0.5 + 1j * (np.random.rand(n_points, 1) - 0.5)) * \ 29 | np.sqrt(6) 30 | else: 31 | noise = np.random.rand(2 ** int(nextpow2(n_points)),) - 0.5 32 | noise = hilbert(noise) / noise.std() / np.sqrt(2) 33 | inds = noise.shape[0] - np.arange(n_points - 1, -1, step=-1) - 1 34 | noise = noise[inds] 35 | return noise 36 | 37 | 38 | def noisecg(n_points, a1=None, a2=None): 39 | """ 40 | Generate analytic complex gaussian noise with mean 0.0 and variance 1.0. 41 | 42 | :param n_points: Length of the desired output signal. 43 | :param a1: 44 | Coefficients of the filter through which the noise is passed. 45 | :param a2: 46 | Coefficients of the filter through which the noise is passed. 47 | :type n_points: int 48 | :type a1: float 49 | :type a2: float 50 | :return: Analytic complex Gaussian noise of length n_points. 51 | :rtype: numpy.ndarray 52 | :Examples: 53 | >>> import matplotlib.pyplot as plt 54 | >>> import numpy as np 55 | >>> noise = noisecg(512) 56 | >>> print("%.1f" % abs((noise ** 2).mean())) 57 | 0.0 58 | >>> print("%.1f" % np.std(noise) ** 2) 59 | 1.0 60 | >>> plt.subplot(211), plt.plot(real(noise)) #doctest: +SKIP 61 | >>> plt.subplot(212), #doctest: +SKIP 62 | >>> plt.plot(linspace(-0.5, 0.5, 512), abs(fftshift(fft(noise))) ** 2) #doctest: +SKIP 63 | 64 | .. plot:: docstring_plots/generators/noise/noisecg.py 65 | """ 66 | assert n_points > 0 67 | if n_points <= 2: 68 | noise = (np.random.randn(n_points, 1.) + 1j * np.random.randn(n_points, 1.)) / np.sqrt(2.) 69 | else: 70 | noise = np.random.normal(size=int(2. ** nextpow2(float(n_points)),)) 71 | noise = hilbert(noise) / noise.std() / np.sqrt(2.) 72 | noise = noise[len(noise) - np.arange(n_points - 1, -1, -1) - 1] 73 | return noise 74 | 75 | 76 | def dopnoise(n_points, s_freq, f_target, distance, v_target, 77 | time_center=None, c=340): 78 | """Generate complex noisy doppler signal, normalized to have unit energy. 79 | 80 | :param n_points: Number of points. 81 | :param s_freq: Sampling frequency. 82 | :param f_target: Frequency of target. 83 | :param distance: Distnace from line to observer. 84 | :param v_target: velocity of target relative to observer. 85 | :param time_center: Time center. (Default n_points / 2) 86 | :param c: Wave velocity (Default 340 m/s) 87 | :type n_points: int 88 | :type s_freq: float 89 | :type f_target: float 90 | :type distance: float 91 | :type v_target: float 92 | :type time_center: float 93 | :type c: float 94 | :return: tuple (output signal, instantaneous frequency law.) 95 | :rtype: tuple(array-like) 96 | :Example: 97 | >>> import numpy as np 98 | >>> from tftb.processing import inst_freq 99 | >>> z, iflaw = dopnoise(500, 200.0, 60.0, 10.0, 70.0, 128.0) 100 | >>> subplot(211), plot(real(z)) #doctest: +SKIP 101 | >>> ifl = inst_freq(z, np.arange(11, 479), 10) 102 | >>> subplot(212), plot(iflaw, 'r', ifl, 'g') #doctest: +SKIP 103 | 104 | .. plot:: docstring_plots/generators/noise/dopnoise.py 105 | """ 106 | if time_center is None: 107 | time_center = np.floor(n_points / 2.0) 108 | 109 | r = 0.9 110 | rr = r ** 2 111 | r2 = 2 * r 112 | vv = v_target ** 2 113 | x = np.random.randn(2 * n_points,) 114 | tmt0 = (np.arange(1, 2 * n_points + 1, dtype=float) - time_center - n_points) / s_freq 115 | dist = np.sqrt(distance ** 2 + (v_target * tmt0) ** 2) 116 | iflaw = (1 - vv * tmt0 / dist / c) * f_target / s_freq 117 | y = np.zeros((2 * n_points,)) 118 | for t in range(2, 2 * n_points): 119 | y[t] = x[t] - rr * (x[t - 2] + y[t - 2]) + r2 * np.cos(2.0 * np.pi * iflaw[t]) * y[t - 1] 120 | y = hilbert(y[(n_points + 1): (2 * n_points + 1)]) /\ 121 | np.sqrt(dist[(n_points + 1): (2 * n_points + 1)]) 122 | y = y / np.sqrt(np.sum(np.abs(y) ** 2)) 123 | iflaw = iflaw[(n_points + 1):(2 * n_points + 1)] 124 | return y, iflaw 125 | 126 | 127 | if __name__ == "__main__": 128 | n = noisecg(128) 129 | -------------------------------------------------------------------------------- /tftb/generators/tests/__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Tests for the generatos module. 11 | """ 12 | -------------------------------------------------------------------------------- /tftb/generators/tests/test_amplitude_modulations.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from numpy import pi 4 | from scipy.signal import argrelmax 5 | from tftb.tests.test_base import TestBase 6 | import tftb.generators.amplitude_modulated as am 7 | 8 | 9 | class TestAmplitudeModulated(TestBase): 10 | 11 | def test_amgauss(self): 12 | """Test if the gaussian amplitude modulator works correctly.""" 13 | time_center = 63 14 | n_points = 128 15 | spread = 10 16 | signal = am.amgauss(n_points, time_center, spread) 17 | # parameters of the underlying gaussian function of the form 18 | # f(x) = a * exp( (-(x - b) **2) / (2 * (c ** 2))) 19 | a, b, c = 1, time_center, spread / np.sqrt(2 * pi) 20 | # Integral of a Gaussian is a * c * sqrt( 2 * pi) 21 | integral = a * c * np.sqrt(2 * pi) 22 | self.assertAlmostEqual(integral, signal.sum()) 23 | 24 | # Other miscellaneous properties of a Gaussian 25 | maximum = argrelmax(signal) 26 | self.assertEqual(len(maximum), 1) 27 | self.assertEqual(maximum[0][0], time_center - 1) 28 | self.assertAlmostEqual(signal[time_center - 1], 1.0) 29 | 30 | self.assert_is_monotonic_increasing(signal[:(time_center - 1)]) 31 | self.assert_is_monotonic_decreasing(signal[(time_center - 1):]) 32 | 33 | infpl1 = np.floor(b - c).astype(int) - 1 34 | infpl2 = np.floor(b + c).astype(int) 35 | self.assert_is_convex(signal[:infpl1]) 36 | self.assert_is_concave(signal[infpl1:infpl2]) 37 | self.assert_is_convex(signal[infpl2:]) 38 | 39 | def test_amexpos(self): 40 | """Test exponential amplitude modulation.""" 41 | n_points, center, spread = 128, 63, 10.0 42 | one_sided = am.amexpos(n_points, center, spread, kind="unilateral") 43 | self.assertEqual(one_sided.max(), 1.0) 44 | self.assert_is_monotonic_decreasing(one_sided[center:]) 45 | two_sided = am.amexpos(n_points, center, spread) 46 | self.assertEqual(two_sided.max(), 1.0) 47 | self.assert_is_monotonic_decreasing(two_sided[center:]) 48 | self.assert_is_monotonic_increasing(two_sided[:center]) 49 | 50 | def test_amrect(self): 51 | """Test rectangular amplitude modulation.""" 52 | n_points, center, spread = 128, 63, 10.0 53 | signal = am.amrect(n_points, center, spread) 54 | self.assertEqual(signal.max(), 1.0) 55 | self.assertEqual(signal[center], 1.0) 56 | np.testing.assert_allclose(np.unique(signal), [0., 1.0]) 57 | 58 | def test_amtriang(self): 59 | """Test triangular amplitude modulation.""" 60 | n_points, center, spread = 128, 63, 10.0 61 | signal = am.amtriang(n_points, center, spread) 62 | self.assertEqual(signal.max(), 1.0) 63 | self.assertEqual(signal[center], 1.0) 64 | self.assert_is_monotonic_decreasing(signal[center:]) 65 | self.assert_is_monotonic_increasing(signal[:center]) 66 | 67 | 68 | if __name__ == "__main__": 69 | unittest.main() 70 | -------------------------------------------------------------------------------- /tftb/generators/tests/test_analytic_signals.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """Tests for the analytic_signals module.""" 10 | 11 | 12 | import unittest 13 | import numpy as np 14 | from scipy.stats import mode 15 | from tftb.tests.test_base import TestBase 16 | from tftb.generators import analytic_signals as ana 17 | 18 | 19 | class TestAnalyticSignals(TestBase): 20 | 21 | def test_anaask(self): 22 | """Test analytic ASK signal.""" 23 | signal, amlaw = ana.anaask(512, 64, 0.05) 24 | self.assert_is_analytic(signal, amlaw) 25 | np.testing.assert_allclose(np.abs(signal), amlaw) 26 | 27 | def test_anabpsk(self): 28 | """Test analytic BPSK signal.""" 29 | signal, amlaw = ana.anabpsk(300, 30, 0.1) 30 | self.assertCountEqual(np.unique(amlaw), [-1, 1]) 31 | self.assert_is_analytic(signal) 32 | 33 | def test_anafsk(self): 34 | """Test analytic phase shift keying.""" 35 | signal, iflaw = ana.anafsk(512, 64, 3) 36 | self.assert_is_analytic(signal) 37 | 38 | def test_anapulse(self): 39 | """Test analytic unit impulse.""" 40 | signal = ana.anapulse(512, 301) 41 | recons = np.zeros((512,)) 42 | recons[301] = 1 43 | np.testing.assert_allclose(recons, np.real(signal), rtol=1e-5, 44 | atol=1e-5) 45 | 46 | def test_anaqpsk(self): 47 | """Test quaternary PSK signal.""" 48 | signal, phases = ana.anaqpsk(512, 64, 0.25) 49 | self.assert_is_analytic(signal) 50 | # Count discontinuities in the signal and the phases and assert that 51 | # they appear in the same locations 52 | uphase = np.unwrap(np.angle(signal)) 53 | dphase = np.diff(uphase) 54 | base_value, _ = mode(dphase) 55 | signal_phase_change = np.abs(dphase - base_value) > 1e-4 56 | ideal_phase_change = np.diff(phases) != 0 57 | np.testing.assert_allclose(signal_phase_change, ideal_phase_change) 58 | 59 | def test_anastep(self): 60 | """Test analytic unit step signal.""" 61 | signal = ana.anastep(256, 128) 62 | recons = np.zeros((256,), dtype=float) 63 | recons[129:] = 1.0 64 | np.testing.assert_allclose(recons, np.real(signal), rtol=1e-5, 65 | atol=1e-5) 66 | 67 | def test_anasing(self): 68 | """Test the analytic singularity signal.""" 69 | signal = ana.anasing(128) 70 | self.assert_is_analytic(signal, amlaw=np.abs(signal)) 71 | 72 | 73 | if __name__ == '__main__': 74 | unittest.main() 75 | -------------------------------------------------------------------------------- /tftb/generators/tests/test_frequency_modulations.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """Tests for the frequency_modulated module.""" 10 | 11 | import unittest 12 | import numpy as np 13 | from tftb.tests.test_base import TestBase 14 | from tftb.generators import frequency_modulated as fm 15 | 16 | 17 | class TestFrequencyModulated(TestBase): 18 | """Tests for the frequency_modulated module.""" 19 | 20 | def test_fmconst(self): 21 | """Test constant frequency modulation.""" 22 | n_points, fnorm, center = 129, 0.5, 64 23 | signal, iflaw = fm.fmconst(n_points, fnorm, center) 24 | real = np.real(signal) 25 | np.testing.assert_allclose(iflaw.ravel(), fnorm * np.ones((n_points,))) 26 | omega = np.arccos(real)[1:] / (2 * np.pi) / np.arange(n_points)[1:] 27 | self.assertEqual(omega.max(), fnorm) 28 | self.assert_is_analytic(signal) 29 | 30 | def test_fmhyp(self): 31 | """Test hyperbolic frequency modulation.""" 32 | n_points, p1, p2 = 128, (1, 0.5), (32, 0.1) 33 | signal, iflaw = fm.fmhyp(n_points, p1, p2) 34 | self.assertEqual(iflaw[p2[0] - 1], p2[1]) 35 | self.assert_is_analytic(signal) 36 | 37 | def test_fmlin(self): 38 | """Test linear frequency modulation.""" 39 | n_points, init_freq, final_freq, center = 128, 0.05, 0.3, 50 40 | signal, iflaw = fm.fmlin(n_points, init_freq, final_freq, center) 41 | np.testing.assert_allclose(iflaw, 42 | np.linspace(init_freq, final_freq, 43 | n_points)) 44 | self.assert_is_analytic(signal) 45 | 46 | def test_fmodany(self): 47 | """Test arbitrary modulation.""" 48 | iflaw = np.pad(np.ones((32,)), pad_width=(48, 48), mode='constant', 49 | constant_values=0) 50 | signal = fm.fmodany(iflaw / 2.0) 51 | self.assert_is_analytic(signal) 52 | np.testing.assert_allclose(np.real(signal)[:48], 53 | np.ones((48,))) 54 | np.testing.assert_allclose(np.real(signal)[-48:], 55 | np.ones((48,))) 56 | x = fm.fmconst(32, 0.5, 1)[0] 57 | np.testing.assert_allclose(np.real(x), np.real(signal)[48:48 + 32]) 58 | 59 | def test_fmpar(self): 60 | """Test parabolic frequency modulation.""" 61 | coeffs = (0.4, -0.0112, 8.6806e-05) 62 | n_points = 128 63 | signal, iflaw = fm.fmpar(n_points, coeffs) 64 | a, b, c = coeffs 65 | xx = np.arange(n_points) 66 | parabola = a + b * xx + c * (xx ** 2) 67 | np.testing.assert_allclose(parabola, iflaw, rtol=1e-3, atol=1e-3) 68 | self.assert_is_analytic(signal) 69 | 70 | def test_fmpower(self): 71 | """Test power law frequency modulation.""" 72 | n_points = 128 73 | degree = 0.5 74 | coefficients = 1, 0.5, 100, 0.1 75 | signal, iflaw = fm.fmpower(n_points, degree, coefficients) 76 | self.assertAlmostEqual(iflaw[coefficients[2]], coefficients[3], 77 | places=1) 78 | self.assert_is_analytic(signal) 79 | 80 | def test_fmsin(self): 81 | """Test sinusoidal frequency modulation.""" 82 | n_points = 140 83 | min_freq, max_freq = 0.05, 0.45 84 | period = 100 85 | center = 20 86 | center_freq = 0.3 87 | freq_dir = -1 88 | signal, iflaw = fm.fmsin(n_points, min_freq, max_freq, period, center, 89 | center_freq, freq_dir) 90 | self.assert_is_analytic(signal) 91 | xx = np.arange(n_points) - center - 4 92 | f_sample = 1.0 / period 93 | yy = freq_dir * np.sin(2 * np.pi * f_sample * xx) 94 | yy = ((max_freq - min_freq) / 2) * yy + (max_freq + min_freq) / 2 95 | np.testing.assert_allclose(yy, iflaw, rtol=1e-3, atol=1e-3) 96 | 97 | 98 | if __name__ == '__main__': 99 | unittest.main() 100 | -------------------------------------------------------------------------------- /tftb/generators/tests/test_misc.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """Tests for the generators.misc module.""" 10 | 11 | import unittest 12 | from tftb.tests.test_base import TestBase 13 | from tftb.generators import misc 14 | from tftb.processing.utils import derive_window 15 | import numpy as np 16 | from scipy.signal import argrelmax, argrelmin 17 | from scipy.signal.windows import hann 18 | 19 | 20 | class TestMisc(TestBase): 21 | 22 | def test_window_derivative(self): 23 | """Test if the derivative of a window function is calculated 24 | properly.""" 25 | window = hann(210) 26 | derivative = derive_window(window) 27 | ix_win_maxima = np.argmax(window) 28 | self.assertAlmostEqual(derivative[ix_win_maxima], 0.0, places=3) 29 | 30 | def test_altes(self): 31 | """Test the altes signal generation.""" 32 | ideal = np.array([0.00200822, -0.21928398, 0.66719239, 0.66719239, 33 | -0.17666382, -0.17009953, -0.038399, -0.00083597]) 34 | actual = misc.altes(8, 0.1, 0.5) 35 | np.testing.assert_allclose(ideal, actual, atol=1e-8, rtol=1e-8) 36 | 37 | def test_doppler(self): 38 | """Test the doppler signal generation.""" 39 | fm, am, iflaw = misc.doppler(512, 200.0, 65, 10, 50) 40 | self.assert_is_monotonic_decreasing(iflaw) 41 | 42 | def test_klauder(self): 43 | """Test the klauder wavelet generation.""" 44 | ideal = np.array([0.14899879, -0.16633309, -0.42806931, 0.16605633, 45 | 0.70769336, 0.16605633, -0.42806931, -0.16633309]) 46 | actual = misc.klauder(8) 47 | np.testing.assert_allclose(ideal, actual, atol=1e-8, rtol=1e-8) 48 | 49 | def test_mexhat(self): 50 | """Test the mexhat wavelet generation.""" 51 | ideal = np.array([-4.36444274e-09, -4.29488427e-04, -1.47862882e-01, 52 | 4.43113463e-01, -1.47862882e-01, -4.29488427e-04, 53 | -4.36444274e-09]) 54 | actual = misc.mexhat(0.5) 55 | np.testing.assert_allclose(ideal, actual, atol=1e-9, rtol=1e-9) 56 | maxima = argrelmax(actual) 57 | self.assertEqual(maxima[0].shape[0], 1) 58 | self.assertEqual(maxima[0][0], 3) 59 | minima = argrelmin(actual) 60 | self.assertCountEqual(minima[0], (2, 4)) 61 | 62 | def test_gdpower(self): 63 | """Test the gdpower generation.""" 64 | ideal_sig = np.array([0.08540661 + 0.05077147j, 0.16735776 + 0.11542816j, 65 | -0.08825763 + 0.17010894j, 0.04412953 - 0.01981114j, 66 | -0.04981628 + 0.34985966j, -0.56798889 - 0.07983783j, 67 | 0.05266730 - 0.57074006j, 0.35650159 - 0.01577918j]) 68 | ideal_f = np.array([0.125, 0.25, 0.375, 0.5]) 69 | ideal_gpd = np.array([8.8, 6.45685425, 5.41880215, 4.8]) 70 | ideals = (ideal_sig, ideal_gpd, ideal_f) 71 | actuals = misc.gdpower(len(ideal_sig), 0.5) 72 | for i, ideal in enumerate(ideals): 73 | actual = actuals[i] 74 | np.testing.assert_allclose(ideal, actual, atol=1e-7, rtol=1e-7) 75 | 76 | 77 | if __name__ == '__main__': 78 | unittest.main() 79 | -------------------------------------------------------------------------------- /tftb/generators/tests/test_noise.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """Tests for the generators.noise module.""" 10 | 11 | import unittest 12 | import numpy as np 13 | from tftb.tests.test_base import TestBase 14 | from tftb.generators import noise 15 | 16 | 17 | class TestNoise(TestBase): 18 | 19 | def test_noisecu(self): 20 | """Test uniform white noise generation.""" 21 | x = noise.noisecu(128) 22 | self.assertAlmostEqual(x.std() ** 2, 1, places=1) 23 | 24 | def test_noisecg(self): 25 | """Test Gaussian white noise generation.""" 26 | x = noise.noisecg(128) 27 | self.assertAlmostEqual(x.std() ** 2, 1, places=1) 28 | 29 | def test_dopnoise(self): 30 | """Test doppler noise generation""" 31 | signal, iflaw = noise.dopnoise(500, 200, 60, 10, 70, 128) 32 | energy = np.sum(np.abs(signal) ** 2) 33 | self.assertAlmostEqual(energy, 1, 3) 34 | self.assert_is_monotonic_decreasing(iflaw) 35 | signal, iflaw = noise.dopnoise(500, 200, 60, 10, 70) 36 | energy = np.sum(np.abs(signal) ** 2) 37 | self.assertAlmostEqual(energy, 1, 3) 38 | self.assert_is_monotonic_decreasing(iflaw) 39 | 40 | 41 | if __name__ == '__main__': 42 | unittest.main() 43 | -------------------------------------------------------------------------------- /tftb/generators/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Tests for tftb.generators.utils 11 | """ 12 | 13 | import unittest 14 | import numpy as np 15 | 16 | from tftb.generators import utils, fmlin 17 | 18 | 19 | class TestUtils(unittest.TestCase): 20 | 21 | def test_sigmerge(self): 22 | """Test merging of signals with a given SNR.""" 23 | signal = fmlin(128)[0] 24 | noise = np.random.randn(128,) 25 | gamma = 0.1 26 | x = utils.sigmerge(signal, noise, gamma) 27 | h_est = np.linalg.norm(signal) / np.linalg.norm(noise) * 10 ** (-gamma / 20) 28 | x_hat = signal + h_est * noise 29 | np.testing.assert_allclose(x, x_hat) 30 | 31 | 32 | if __name__ == '__main__': 33 | unittest.main() 34 | -------------------------------------------------------------------------------- /tftb/generators/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.signal import hilbert 3 | from scipy.integrate import trapezoid 4 | # from tftb.utils import nextpow2 5 | 6 | 7 | def sigmerge(x1, x2, ratio=0.0): 8 | """ 9 | Add two signals with a specific energy ratio in decibels. 10 | 11 | :param x1: 1D numpy.ndarray 12 | :param x2: 1D numpy.ndarray 13 | :param ratio: Energy ratio in decibels. 14 | :type x1: numpy.ndarray 15 | :type x2: numpy.ndarray 16 | :type ratio: float 17 | :return: The merged signal 18 | :rtype: numpy.ndarray 19 | """ 20 | assert x1.ndim == 1 21 | assert x2.ndim == 1 22 | assert type(ratio) in (float, int) 23 | ex1 = np.mean(np.abs(x1) ** 2) 24 | ex2 = np.mean(np.abs(x2) ** 2) 25 | h = np.sqrt(ex1 / (ex2 * 10 ** (ratio / 10.0))) 26 | sig = x1 + h * x2 27 | return sig 28 | 29 | 30 | def scale(X, a, fmin, fmax, N): 31 | """Scale a signal with the Mellin transform. 32 | 33 | :param X: signal to be scaled. 34 | :param a: scale factor 35 | :param fmin: lower frequency bound 36 | :param fmax: higher frequency bound 37 | :param N: number of analyzed voices 38 | :type X: array-like 39 | :type a: float 40 | :type fmin: float 41 | :type fmax: float 42 | :type N: int 43 | :return: A-scaled version of X. 44 | :rtype: array-like 45 | """ 46 | Z = hilbert(np.real(X)) 47 | T = X.shape[0] 48 | M = (T + np.remainder(T, 2)) / 2 49 | 50 | # B = fmax - fmin 51 | # R = B / ((fmin + fmax) / 2) 52 | # Nq = np.ceil((B * T * (1 + 2.0 / R)) * np.log()) 53 | # Nmin = Nq - np.remainder(Nq, 2) 54 | # Ndflt = 2 ** nextpow2(Nmin) 55 | 56 | # Geometric sampling of the analyzed spectrum 57 | k = np.arange(N) 58 | q = (fmax / float(fmin)) ** (1.0 / (N - 1.0)) 59 | geo_f = fmin * np.exp((k - 1) * np.log(q)) 60 | t = np.arange(X.shape[0]) - M - 1 61 | tfmatx = np.exp(-2 * 1j * np.dot(t.reshape(128, 1), geo_f.reshape(1, 128)) * np.pi) 62 | ZS = np.dot(Z, tfmatx) 63 | ZS = np.hstack((ZS, np.zeros((N,)))) 64 | 65 | # Mellin transform computation of the analyzed signal 66 | p = np.arange(2 * N) 67 | MS = np.fft.fftshift(np.fft.ifft(ZS, axis=0)) 68 | beta = (p / float(N) - 1.0) / (2 * np.log(q)) 69 | 70 | # Inverse mellin and fourier transform. 71 | Mmax = np.amax(np.ceil(X.shape[0] / 2.0 * a)) 72 | if isinstance(a, np.ndarray): 73 | S = np.zeros((2 * Mmax, a.shape[0]), dtype=complex) 74 | else: 75 | S = np.zeros((2 * Mmax,), dtype=complex) 76 | ptr = 0 77 | DMS = np.exp(-2 * np.pi * 1j * beta * np.log(a)) * MS 78 | DS = np.fft.fft(np.fft.fftshift(DMS), axis=0) 79 | Mcurrent = np.ceil(a * X.shape[0] / 2) 80 | t = np.arange(-Mcurrent, Mcurrent) - 1 81 | itfmatx = np.exp(2 * 1j * np.pi * np.dot(t.reshape((256, 1)), 82 | geo_f.reshape((1, 128)))) 83 | dilate_sig = np.zeros((2 * Mcurrent,), dtype=complex) 84 | for kk in range(2 * int(Mcurrent)): 85 | dilate_sig[kk] = trapezoid(itfmatx[kk, :] * DS[:N], geo_f) 86 | S[(Mmax - Mcurrent):(Mmax + Mcurrent)] = dilate_sig 87 | ptr += 1 88 | 89 | S = S * np.linalg.norm(X) / np.linalg.norm(S) 90 | return S 91 | 92 | 93 | if __name__ == '__main__': 94 | from tftb.generators import altes 95 | sig = altes(256, 0.1, 0.45, 10000) 96 | -------------------------------------------------------------------------------- /tftb/processing/__init__.py: -------------------------------------------------------------------------------- 1 | from tftb.processing.time_domain import loctime 2 | from tftb.processing.freq_domain import locfreq, inst_freq, group_delay 3 | from tftb.processing.plotifl import plotifl 4 | from tftb.processing.cohen import ( 5 | WignerVilleDistribution, PseudoWignerVilleDistribution, 6 | smoothed_pseudo_wigner_ville, MargenauHillDistribution, Spectrogram) 7 | from tftb.processing.reassigned import spectrogram as reassigned_spectrogram 8 | from tftb.processing.reassigned import smoothed_pseudo_wigner_ville as \ 9 | reassigned_smoothed_pseudo_wigner_ville 10 | from tftb.processing.postprocessing import ideal_tfr, renyi_information 11 | from tftb.processing.affine import ( 12 | Scalogram, BertrandDistribution, DFlandrinDistribution, UnterbergerDistribution) 13 | from tftb.processing.linear import ShortTimeFourierTransform 14 | 15 | __all__ = ['loctime', 'locfreq', 'inst_freq', 'group_delay', 'plotifl', 16 | 'WignerVilleDistribution', 'PseudoWignerVilleDistribution', 17 | 'smoothed_pseudo_wigner_ville', 'MargenauHillDistribution', 'Spectrogram', 18 | 'reassigned_spectrogram', 'reassigned_smoothed_pseudo_wigner_ville', 19 | 'ideal_tfr', 'renyi_information', 'Scalogram', 'BertrandDistribution', 20 | 'DFlandrinDistribution', 'UnterbergerDistribution', 'ShortTimeFourierTransform'] 21 | -------------------------------------------------------------------------------- /tftb/processing/ambiguity.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Ambiguity functions. 11 | """ 12 | 13 | import numpy as np 14 | from scipy.signal import hilbert 15 | from tftb.utils import nextpow2 16 | 17 | 18 | def _find(condt): 19 | res, = np.nonzero(np.ravel(condt)) 20 | return res 21 | 22 | 23 | def wide_band(signal, fmin=None, fmax=None, N=None): 24 | if 1 in signal.shape: 25 | signal = signal.ravel() 26 | elif signal.ndim != 1: 27 | raise ValueError("The input signal should be one dimensional.") 28 | s_ana = hilbert(np.real(signal)) 29 | nx = signal.shape[0] 30 | m = int(np.round(nx / 2.0)) 31 | t = np.arange(nx) - m 32 | tmin = 0 33 | tmax = nx - 1 34 | T = tmax - tmin 35 | 36 | # determine default values for fmin, fmax 37 | if (fmin is None) or (fmax is None): 38 | STF = np.fft.fftshift(s_ana) 39 | sp = np.abs(STF[:m]) ** 2 40 | maxsp = np.amax(sp) 41 | f = np.linspace(0, 0.5, m + 1) 42 | f = f[:m] 43 | indmin = _find(sp > maxsp / 100.0).min() 44 | indmax = _find(sp > maxsp / 100.0).max() 45 | if fmin is None: 46 | fmin = max([0.01, 0.05 * np.fix(f[indmin] / 0.05)]) 47 | if fmax is None: 48 | fmax = 0.05 * np.ceil(f[indmax] / 0.05) 49 | B = fmax - fmin 50 | R = B / ((fmin + fmax) / 2.0) 51 | nq = np.ceil((B * T * (1 + 2.0 / R) * np.log((1 + R / 2.0) / (1 - R / 2.0))) / 2.0) # NOQA 52 | nmin = nq - (nq % 2) 53 | if N is None: 54 | N = int(2 ** (nextpow2(nmin))) 55 | 56 | # geometric sampling for the analyzed spectrum 57 | k = np.arange(1, N + 1) 58 | q = (fmax / fmin) ** (1.0 / (N - 1)) 59 | geo_f = fmin * (np.exp((k - 1) * np.log(q))) 60 | tfmatx = -2j * np.dot(t.reshape(-1, 1), geo_f.reshape(1, -1)) * np.pi 61 | tfmatx = np.exp(tfmatx) 62 | S = np.dot(s_ana.reshape(1, -1), tfmatx) 63 | S = np.tile(S, (nx, 1)) 64 | Sb = S * tfmatx 65 | 66 | tau = t 67 | S = np.c_[S, np.zeros((nx, N))].T 68 | Sb = np.c_[Sb, np.zeros((nx, N))].T 69 | 70 | # mellin transform computation of the analyzed signal 71 | p = np.arange(2 * N) 72 | coef = np.exp(p / 2.0 * np.log(q)) 73 | mellinS = np.fft.fftshift(np.fft.ifft(S[:, 0] * coef)) 74 | mellinS = np.tile(mellinS, (nx, 1)).T 75 | 76 | mellinSb = np.zeros((2 * N, nx), dtype=complex) 77 | for i in range(nx): 78 | mellinSb[:, i] = np.fft.fftshift(np.fft.ifft(Sb[:, i] * coef)) 79 | 80 | k = np.arange(1, 2 * N + 1) 81 | scale = np.logspace(np.log10(fmin / fmax), np.log10(fmax / fmin), N) 82 | theta = np.log(scale) 83 | mellinSSb = mellinS * np.conj(mellinSb) 84 | 85 | waf = np.fft.ifft(mellinSSb, N, axis=0) 86 | no2 = int((N + N % 2) / 2.0) 87 | waf = np.r_[waf[no2:(N + 1), :], waf[:no2, :]] 88 | 89 | # normalization 90 | s = np.real(s_ana) 91 | SP = np.fft.fft(hilbert(s)) 92 | indmin = int(1 + np.round(fmin * (nx - 2))) 93 | indmax = int(1 + np.round(fmax * (nx - 2))) 94 | sp_ana = SP[(indmin - 1):indmax] 95 | waf *= (np.linalg.norm(sp_ana) ** 2) / waf[no2 - 1, m - 1] / N 96 | 97 | return waf, tau, theta 98 | 99 | 100 | def narrow_band(signal, lag=None, n_fbins=None): 101 | """Narrow band ambiguity function. 102 | 103 | :param signal: Signal to be analyzed. 104 | :param lag: vector of lag values. 105 | :param n_fbins: number of frequency bins 106 | :type signal: array-like 107 | :type lag: array-like 108 | :type n_fbins: int 109 | :return: Doppler lag representation 110 | :rtype: array-like 111 | """ 112 | 113 | n = signal.shape[0] 114 | if lag is None: 115 | if n % 2 == 0: 116 | tau_start, tau_end = -n / 2 + 1, n / 2 117 | else: 118 | tau_start, tau_end = -(n - 1) / 2, (n + 1) / 2 119 | lag = np.arange(tau_start, tau_end) 120 | taucol = lag.shape[0] 121 | 122 | if n_fbins is None: 123 | n_fbins = signal.shape[0] 124 | 125 | naf = np.zeros((n_fbins, taucol), dtype=complex) 126 | for icol in range(taucol): 127 | taui = int(lag[icol]) 128 | t = np.arange(abs(taui), n - abs(taui)).astype(int) 129 | naf[t, icol] = signal[t + taui] * np.conj(signal[t - taui]) 130 | naf = np.fft.fft(naf, axis=0) 131 | 132 | _ix1 = np.arange((n_fbins + (n_fbins % 2)) // 2, n_fbins) 133 | _ix2 = np.arange((n_fbins + (n_fbins % 2)) // 2) 134 | 135 | _xi1 = -(n_fbins - (n_fbins % 2)) // 2 136 | _xi2 = ((n_fbins + (n_fbins % 2)) // 2 - 1) 137 | xi = np.arange(_xi1, _xi2 + 1, dtype=float) / n_fbins 138 | naf = naf[np.hstack((_ix1, _ix2)), :] 139 | return naf, lag, xi 140 | 141 | 142 | if __name__ == '__main__': 143 | from tftb.generators.misc import altes 144 | sig = altes(128, 0.1, 0.45) 145 | waf, tau, theta = wide_band(sig) 146 | from matplotlib.pyplot import contour, show 147 | contour(tau, theta, np.abs(waf) ** 2) 148 | show() 149 | -------------------------------------------------------------------------------- /tftb/processing/freq_domain.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def locfreq(signal): 5 | """ 6 | Compute the frequency localization characteristics. 7 | 8 | :param sig: input signal 9 | :type sig: numpy.ndarray 10 | 11 | :return: average normalized frequency center, frequency spreading 12 | :rtype: tuple 13 | 14 | :Example: 15 | >>> from tftb.generators import amgauss 16 | >>> z = amgauss(160, 80, 50) 17 | >>> fm, B = locfreq(z) 18 | >>> print("%.3g" % fm) 19 | -9.18e-14 20 | >>> print("%.4g" % B) 21 | 0.02 22 | """ 23 | if signal.ndim > 1: 24 | if 1 not in signal.shape: 25 | raise TypeError 26 | else: 27 | signal = signal.ravel() 28 | no2r = np.round(signal.shape[0] / 2.0) 29 | no2f = np.floor(signal.shape[0] / 2.0) 30 | sig = np.fft.fft(signal) 31 | sig = np.abs(sig) ** 2 32 | sig = sig / sig.mean() 33 | freqs = np.hstack((np.arange(no2f), np.arange(-no2r, 0))) / signal.shape[0] 34 | fm = np.mean(freqs * sig) 35 | bw = 2 * np.sqrt(np.pi * np.mean(((freqs - fm) ** 2) * sig)) 36 | return fm, bw 37 | 38 | 39 | def inst_freq(x, t=None, L=1): 40 | """ 41 | Compute the instantaneous frequency of an analytic signal at specific 42 | time instants using the trapezoidal integration rule. 43 | 44 | :param x: The input analytic signal 45 | :param t: The time instants at which to calculate the instantaneous frequencies. 46 | :param L: Non default values are currently not supported. 47 | If L is 1, the normalized instantaneous frequency is computed. If L > 1, 48 | the maximum likelihood estimate of the instantaneous frequency of the 49 | deterministic part of the signal. 50 | :type x: numpy.ndarray 51 | :type t: numpy.ndarray 52 | :type L: int 53 | 54 | :return: instantaneous frequencies of the input signal. 55 | :rtype: numpy.ndarray 56 | 57 | :Example: 58 | >>> from tftb.generators import fmsin 59 | >>> x = fmsin(70, 0.05, 0.35, 25)[0] 60 | >>> instf, timestamps = inst_freq(x) 61 | >>> plot(timestamps, instf) #doctest: +SKIP 62 | 63 | .. plot:: docstring_plots/processing/freq_domain/inst_freq.py 64 | """ 65 | if x.ndim != 1: 66 | if 1 not in x.shape: 67 | raise TypeError("Input should be a one dimensional array.") 68 | else: 69 | x = x.ravel() 70 | if t is not None: 71 | if t.ndim != 1: 72 | if 1 not in t.shape: 73 | raise TypeError("Time instants should be a one dimensional " 74 | "array.") 75 | else: 76 | t = t.ravel() 77 | else: 78 | t = np.arange(2, len(x)) 79 | 80 | fnorm = 0.5 * (np.angle(-x[t] * np.conj(x[t - 2])) + np.pi) / (2 * np.pi) 81 | return fnorm, t 82 | 83 | 84 | def group_delay(x, fnorm=None): 85 | """ 86 | Compute the group delay of a signal at normalized frequencies. 87 | 88 | :param x: time domain signal 89 | :param fnorm: normalized frequency at which to calculate the group delay. 90 | :type x: numpy.ndarray 91 | :type fnorm: float 92 | 93 | :return: group delay 94 | :rtype: numpy.ndarray 95 | 96 | :Example: 97 | >>> import numpy as np 98 | >>> from tftb.generators import amgauss, fmlin 99 | >>> x = amgauss(128, 64.0, 30) * fmlin(128, 0.1, 0.4)[0] 100 | >>> fnorm = np.arange(0.1, 0.38, step=0.04) 101 | >>> gd = group_delay(x, fnorm) 102 | >>> plot(gd, fnorm) #doctest: +SKIP 103 | 104 | .. plot:: docstring_plots/processing/freq_domain/group_delay.py 105 | """ 106 | if x.ndim != 1: 107 | if 1 not in x.shape: 108 | raise TypeError 109 | else: 110 | x = x.ravel() 111 | 112 | if fnorm is None: 113 | numerator = np.fft.fft(x * np.arange(1, x.shape[0] + 1)) 114 | denominator = np.fft.fft(x) 115 | window = np.real(numerator / denominator) >= 1 116 | ratio = np.real(numerator / denominator) * window.astype(int) 117 | ratio = ratio * (np.real(numerator / denominator) <= (len(x) + 3)).astype(int) 118 | gd = np.fft.fftshift(ratio) 119 | else: 120 | exponent = np.exp(-1j * 2.0 * np.pi * fnorm.reshape(len(fnorm), 1) * np.arange(len(x))) 121 | numerator = np.dot(exponent, (x * np.arange(1, x.shape[0] + 1))) 122 | denominator = np.dot(exponent, x) 123 | window = np.real(numerator / denominator) >= 1 124 | ratio = np.real(numerator / denominator) * window.astype(int) 125 | gd = ratio * (np.real(numerator / denominator) <= (len(x) + 3)).astype(int) 126 | return gd 127 | -------------------------------------------------------------------------------- /tftb/processing/plotifl.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | from tftb.processing.freq_domain import inst_freq 16 | from tftb.generators import fmlin 17 | 18 | 19 | def plotifl(time_instants, iflaws, signal=None, **kwargs): 20 | """Plot normalized instantaneous frequency laws. 21 | 22 | :param time_instants: timestamps of the signal 23 | :param iflaws: instantaneous freqency law(s) of the signal. 24 | :param signal: if provided, display it. 25 | :type time_instants: array-like 26 | :type iflaws: array-like 27 | :type signal: array-like 28 | :return: None 29 | """ 30 | indices = np.logical_not(np.isnan(iflaws)) 31 | minif = np.amin(iflaws[indices]) 32 | fig = plt.figure() 33 | if signal is not None: 34 | axsig = fig.add_subplot(211) 35 | axsig.set_position([0.10, 0.69, 0.80, 0.25]) 36 | plt.sca(axsig) 37 | plt.plot(time_instants, np.real(signal)) 38 | plt.title('Signal') 39 | plt.grid(True) 40 | plt.xlim(time_instants.min(), time_instants.max()) 41 | axtfr = fig.add_subplot(212) 42 | axtfr.set_position([0.10, 0.21, 0.80, 0.45]) 43 | plt.sca(axtfr) 44 | plt.plot(time_instants, iflaws) 45 | plt.xlim(time_instants.min(), time_instants.max()) 46 | plt.grid(kwargs.get('grid', True)) 47 | if minif >= 0: 48 | plt.ylim(0, 0.5) 49 | else: 50 | plt.ylim(-0.5, 0.5) 51 | plt.xlabel('Time') 52 | plt.ylabel('Normalized frequency') 53 | plt.title('Instantaneous frequency law(s)') 54 | plt.show() 55 | 56 | 57 | if __name__ == '__main__': 58 | signal, _ = fmlin(256) 59 | time_samples = np.arange(3, 257) 60 | ifr = inst_freq(signal) 61 | plotifl(time_samples, ifr) 62 | -------------------------------------------------------------------------------- /tftb/processing/postprocessing.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Postprocessing functions. 11 | """ 12 | 13 | import numpy as np 14 | from tftb.processing.utils import integrate_2d 15 | 16 | 17 | def hough_transform(image, m=None, n=None): 18 | """hough_transform 19 | 20 | :param image: 21 | :param m: 22 | :param n: 23 | :type image: 24 | :type m: 25 | :type n: 26 | :return: 27 | :rtype: 28 | """ 29 | xmax, ymax = image.shape 30 | if m is None: 31 | m = xmax 32 | if n is None: 33 | n = ymax 34 | 35 | rhomax = np.sqrt((xmax ** 2) + (ymax ** 2)) / 2.0 36 | deltar = rhomax / (m - 1.0) 37 | deltat = 2 * np.pi / n 38 | 39 | ht = np.zeros((m, n)) 40 | imax = np.amax(image) 41 | 42 | if xmax % 2 != 0: 43 | xc = (xmax + 1) // 2 44 | xf = xc - 1 45 | else: 46 | xc = xf = xmax // 2 47 | x0 = 1 - xc 48 | 49 | if ymax % 2 != 0: 50 | yc = (ymax + 1) // 2 51 | yf = yc - 1 52 | else: 53 | yc = yf = ymax // 2 54 | y0 = 1 - yc 55 | 56 | for x in range(x0, xf + 1): 57 | for y in range(y0, yf + 1): 58 | if np.abs(image[x + xc - 1, y + yc - 1]) > imax / 20.0: 59 | for theta in np.linspace(0, 2 * np.pi - deltat, n): 60 | rho = x * np.cos(theta) - y * np.sin(theta) 61 | if (rho >= 0) and (rho <= rhomax): 62 | ht[int(np.round(rho / deltar)), 63 | int(np.round(theta / deltat))] += image[x + xc - 1, 64 | y + yc - 1] 65 | rho = np.linspace(0, rhomax, n) 66 | theta = np.linspace(0, 2 * np.pi - deltat, n) 67 | return ht, rho, theta 68 | 69 | 70 | def renyi_information(tfr, timestamps=None, freq=None, alpha=3.0): 71 | """renyi_information 72 | 73 | :param tfr: 74 | :param timestamps: 75 | :param freq: 76 | :param alpha: 77 | :type tfr: 78 | :type timestamps: 79 | :type freq: 80 | :type alpha: 81 | :return: 82 | :rtype: 83 | """ 84 | if alpha == 1 and tfr.min().min() < 0: 85 | raise ValueError("Distribution with negative values not allowed.") 86 | m, n = tfr.shape 87 | if timestamps is None: 88 | timestamps = np.arange(n) + 1 89 | elif np.allclose(timestamps, np.arange(n)): 90 | timestamps += 1 91 | if freq is None: 92 | freq = np.arange(m) 93 | freq.sort() 94 | tfr = tfr / integrate_2d(tfr, timestamps, freq) 95 | if alpha == 1: 96 | R = -integrate_2d(tfr * np.log2(tfr + np.spacing(1)), timestamps, freq) 97 | else: 98 | R = np.log2(integrate_2d(tfr ** alpha, timestamps, freq) + np.spacing(1)) 99 | R = R / (1 - alpha) 100 | return R 101 | 102 | 103 | def ideal_tfr(iflaws, timestamps=None, n_fbins=None): 104 | """ideal_tfr 105 | 106 | :param iflaws: 107 | :param timestamps: 108 | :param n_fbins: 109 | :type iflaws: 110 | :type timestamps: 111 | :type n_fbins: 112 | :return: 113 | :rtype: 114 | """ 115 | ifrow = iflaws.shape[0] 116 | if timestamps is None: 117 | timestamps = np.arange(iflaws[0, :].shape[0]) 118 | if n_fbins is None: 119 | n_fbins = iflaws[0, :].shape[0] 120 | 121 | tcol = timestamps.shape[0] 122 | 123 | tfr = np.zeros((n_fbins, tcol)) 124 | for icol in range(tcol): 125 | ti = timestamps[icol] 126 | for fi in range(ifrow): 127 | if np.isnan(iflaws[fi, ti]): 128 | tfr[ti, fi] = np.nan 129 | else: 130 | tfr[int(np.round(iflaws[fi, ti] * 2 * (n_fbins - 1))), icol] = 1 131 | freqs = np.arange(n_fbins, dtype=float) / n_fbins * 0.5 132 | return tfr, timestamps, freqs 133 | 134 | 135 | def friedman_density(tfr, re_mat, timestamps=None): 136 | """friedman_density 137 | 138 | :param tfr: 139 | :param re_mat: 140 | :param timestamps: 141 | :type tfr: 142 | :type re_mat: 143 | :type timestamps: 144 | :return: 145 | :rtype: 146 | """ 147 | tfrrow, tfrcol = tfr.shape 148 | if timestamps is None: 149 | timestamps = np.arange(tfrcol) 150 | 151 | tifd = np.zeros((tfrrow, tfrcol)) 152 | bins = 0.5 + np.arange(tfrrow) 153 | bin_edges = np.r_[-np.Inf, 0.5 * (bins[:-1] + bins[1:]), np.Inf] 154 | threshold = np.sum(np.sum(tfr)) * 0.5 / tfr.size 155 | 156 | for j in range(tfrcol): 157 | indices = tfr[:, j] > threshold 158 | if np.any(indices): 159 | occurences, _ = np.histogram(np.real(re_mat[indices, j]), bin_edges) 160 | tifd[:, j] = occurences 161 | tifd = tifd / np.sum(np.sum(tifd)) 162 | return tifd 163 | 164 | 165 | def ridges(tfr, re_mat, timestamps=None, method='rsp'): 166 | """ridges 167 | 168 | :param tfr: 169 | :param re_mat: 170 | :param timestamps: 171 | :param method: 172 | :type tfr: 173 | :type re_mat: 174 | :type timestamps: 175 | :type method: 176 | :return: 177 | :rtype: 178 | """ 179 | method = method.lower() 180 | tfrrow, tfrcol = tfr.shape 181 | if timestamps is None: 182 | timestamps = np.arange(tfrcol) 183 | 184 | n_fbins = tfrrow 185 | freqs = np.arange(n_fbins) 186 | threshold = np.sum(np.sum(tfr)) * 0.5 / tfr.size 187 | 188 | if method in ('rpwv', 'rpmh'): 189 | for icol in range(tfrcol): 190 | ti = timestamps[icol] 191 | indices = np.logical_and(tfr[:, icol] > threshold, 192 | re_mat[:, icol] - freqs == 0) 193 | if np.any(indices): 194 | time_points = np.ones((indices.sum(),)) * ti 195 | freq_points = np.arange(indices.shape[0])[indices] / (2.0 * n_fbins) 196 | elif method == "rspwv": 197 | for icol in range(tfrcol): 198 | ti = timestamps[icol] 199 | condt1 = np.real(re_mat[:, icol]) - freqs == 0 200 | condt2 = np.imag(re_mat[:, icol]) - icol == 0 201 | condt3 = tfr[:, icol] > threshold 202 | indices = np.logical_and(condt1, condt2, condt3) 203 | if np.any(indices): 204 | time_points = np.ones((indices.sum(),)) * ti 205 | freq_points = np.arange(indices.shape[0])[indices] / (2.0 * n_fbins) 206 | elif method in ("rsp", "type1"): 207 | for icol in range(tfrcol): 208 | ti = timestamps[icol] 209 | condt1 = np.real(re_mat[:, icol]) - freqs == 0 210 | condt2 = np.imag(re_mat[:, icol]) - icol == 0 211 | condt3 = tfr[:, icol] > threshold 212 | indices = np.logical_and(condt1, condt2, condt3) 213 | if np.any(indices): 214 | time_points = np.ones((indices.sum(),)) * ti 215 | freq_points = np.arange(indices.shape[0])[indices] / n_fbins 216 | else: 217 | raise ValueError("Unknown time frequency representation.") 218 | return time_points, freq_points 219 | 220 | 221 | if __name__ == '__main__': 222 | from tftb.generators import atoms 223 | from tftb.processing import Spectrogram 224 | s = atoms(64, np.array([[32, 0.3, 16, 1]])) 225 | spec = Spectrogram(s) 226 | tfr, t, f = spec.run() 227 | print(renyi_information(tfr, t, f, 3)) 228 | -------------------------------------------------------------------------------- /tftb/processing/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-signal/tftb/18c784d9a8539436165aff6f6ca5f100b116ee8b/tftb/processing/tests/__init__.py -------------------------------------------------------------------------------- /tftb/processing/tests/test_ambiguity.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Tests for tftb.processing.ambiguity 11 | """ 12 | 13 | import unittest 14 | import numpy as np 15 | 16 | from tftb.generators import fmlin, amgauss, altes 17 | from tftb.processing import ambiguity 18 | from tftb.tests.test_base import TestBase 19 | 20 | 21 | class TestWideBand(TestBase): 22 | """Tests for the wide band ambiguity function.""" 23 | def setUp(self): 24 | self.signal = altes(128, 0.1, 0.45) 25 | self.tfr, self.tau, self.theta = ambiguity.wide_band(self.signal) 26 | 27 | def test_max_value(self): 28 | """Test if the maximum value of the ambiguity function occurs at the 29 | origin""" 30 | max_ix = np.argmax(np.abs(self.tfr)) 31 | self.assertAlmostEqual(float(self.tfr.size) / max_ix, 2, places=1) 32 | 33 | 34 | class TestNarrowBand(TestBase): 35 | """Tests for the narrow band ambiguity function""" 36 | 37 | def setUp(self): 38 | x = fmlin(64, 0.2, 0.5)[0] * amgauss(64) 39 | y = fmlin(64, 0.3, 0)[0] * amgauss(64) 40 | self.signal = np.hstack((x, y)) 41 | self.tfr, self.lag, self.doppler = ambiguity.narrow_band(self.signal) 42 | 43 | def test_max_value(self): 44 | """Test if the maximum value of the ambiguity function occurs at the 45 | origin""" 46 | xorg, yorg = map(lambda x: int(x / 2), self.tfr.shape) 47 | abs_mat = np.abs(self.tfr) ** 2 48 | max_val = abs_mat[xorg, yorg] 49 | for i in range(abs_mat.shape[0]): 50 | for j in range(abs_mat.shape[1]): 51 | self.assertTrue(abs_mat[i, j] <= max_val) 52 | 53 | def test_volume_invariance(self): 54 | """Test the volume invariance property of the narrow band ambiguity 55 | function.""" 56 | volume = np.abs(self.tfr[self.doppler == 0, self.lag == 0]) ** 2 57 | volume_integral = (np.abs(self.tfr) ** 2).sum().sum() 58 | self.assertAlmostEqual(volume[0], 59 | volume_integral / (self.signal.shape[0] / 2)) 60 | 61 | @unittest.skip("Not quite ready yet.") 62 | def test_symmetry(self): 63 | """Test the symmetry property of the narrow band ambiguity function.""" 64 | tfr = self.tfr[1:, :] 65 | self.assert_is_hermitian(tfr) 66 | 67 | 68 | if __name__ == '__main__': 69 | unittest.main() 70 | -------------------------------------------------------------------------------- /tftb/processing/tests/test_cohen.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Tests for tftb.processing.cohen 11 | """ 12 | 13 | import unittest 14 | import numpy as np 15 | from scipy.signal.windows import kaiser 16 | from tftb.processing import cohen 17 | from tftb.generators import fmsin, fmlin 18 | from tftb.tests.test_base import TestBase 19 | 20 | 21 | class TestCohen(TestBase): 22 | 23 | def test_page_reality(self): 24 | """Test the reality property of the Page distribution.""" 25 | signal, _ = fmsin(128) 26 | signal = signal / 128.0 27 | tfr, _, _ = cohen.PageRepresentation(signal).run() 28 | self.assertTrue(np.all(np.isreal(tfr))) 29 | 30 | def test_spectrogram_time_invariance(self): 31 | """Test the time invariance property of the spectrogram.""" 32 | signal, _ = fmlin(128, 0.1, 0.4) 33 | window = kaiser(17, 3 * np.pi) 34 | tfr, ts, freqs = cohen.Spectrogram(signal, n_fbins=64, fwindow=window).run() 35 | shift = 64 36 | timeshifted_signal = np.roll(signal, shift) 37 | timeshifted_tfr, _, _ = cohen.Spectrogram(timeshifted_signal, n_fbins=64, 38 | fwindow=window).run() 39 | rolled_tfr = np.roll(tfr, shift, axis=1) 40 | # the time invariance property holds mostly qualitatively. The shifted 41 | # TFR is not numerically indentical to the rolled TFR, having 42 | # differences at the edges; so clip with two TFRs where there are 43 | # discontinuities in the TFR. 44 | edge = 10 45 | xx = np.c_[timeshifted_tfr[:, edge:(shift - edge)], 46 | timeshifted_tfr[:, (shift + edge):-edge]] 47 | yy = np.c_[rolled_tfr[:, edge:(shift - edge)], 48 | rolled_tfr[:, (shift + edge):-edge]] 49 | np.testing.assert_allclose(xx, yy) 50 | 51 | def test_spectrogram_non_negativity(self): 52 | """Test that the spectrogram is non negative.""" 53 | signal, _ = fmlin(128, 0.1, 0.4) 54 | window = kaiser(17, 3 * np.pi) 55 | tfr, _, _ = cohen.Spectrogram(signal, n_fbins=64, fwindow=window).run() 56 | self.assertTrue(np.all(tfr >= 0)) 57 | 58 | def test_spectrogram_energy_conservation(self): 59 | """Test the energy conservation property of the spectrogram.""" 60 | signal, _ = fmlin(128, 0.1, 0.4) 61 | window = kaiser(17, 3 * np.pi) 62 | tfr, ts, freqs = cohen.Spectrogram(signal, n_fbins=64, fwindow=window).run() 63 | e_sig = (np.abs(signal) ** 2).sum() 64 | self.assertAlmostEqual(tfr.sum().sum() / 64, e_sig) 65 | 66 | def test_spectrogram_reality(self): 67 | """Test the reality property of the spectrogram.""" 68 | signal, _ = fmlin(128, 0.1, 0.4) 69 | window = kaiser(17, 3 * np.pi) 70 | tfr, _, _ = cohen.Spectrogram(signal, n_fbins=64, fwindow=window).run() 71 | self.assertTrue(np.all(np.isreal(tfr))) 72 | 73 | def test_spectrogram_linearity(self): 74 | """Test the linearity property of the spectrogram.""" 75 | signal, _ = fmlin(128, 0.1, 0.4) 76 | window = kaiser(17, 3 * np.pi) 77 | tfr1, _, _ = cohen.Spectrogram(signal, n_fbins=64, 78 | fwindow=window).run() 79 | tfr2, _, _ = cohen.Spectrogram(signal * 2, n_fbins=64, 80 | fwindow=window).run() 81 | x = np.sum(np.sum(tfr2)) 82 | y = np.sum(np.sum(tfr1)) 83 | self.assertEqual(x / y, 4) 84 | 85 | def test_wigner_ville_energy(self): 86 | """Test the energy property of the Wigner Ville representation.""" 87 | signal, _ = fmsin(128) 88 | signal = signal / 128.0 89 | tfr, _, _ = cohen.WignerVilleDistribution(signal).run() 90 | x = np.sum(np.sum(tfr)) 91 | y = np.sum(np.abs(signal) ** 2) * 128 92 | self.assertEqual(x, y) 93 | 94 | def test_wigner_ville_projection(self): 95 | """Test the projection property of the Wigner Ville representation.""" 96 | signal, _ = fmsin(128) 97 | tfr, _, _ = cohen.WignerVilleDistribution(signal).run() 98 | x = np.abs(signal) ** 2 99 | y = np.sum(tfr, axis=0) / 128 100 | np.testing.assert_allclose(x, y) 101 | 102 | def test_reality(self): 103 | """Test the reality property of the Wigner Ville representation.""" 104 | signal, _ = fmsin(128) 105 | tfr, _, _ = cohen.WignerVilleDistribution(signal).run() 106 | self.assertTrue(np.all(np.isreal(tfr))) 107 | 108 | def test_wigner_ville_regionprops(self): 109 | """Test the regional property of the Wigner Ville representation.""" 110 | signal, _ = fmsin(128) 111 | signal[64:] = 0 112 | tfr, _, _ = cohen.WignerVilleDistribution(signal).run() 113 | self.assertTrue(np.all(tfr[:, 64:] == 0)) 114 | 115 | signal, _ = fmsin(128) 116 | signal[:64] = 0 117 | tfr, _, _ = cohen.WignerVilleDistribution(signal).run() 118 | self.assertTrue(np.all(tfr[:, :64] == 0)) 119 | 120 | def test_pseudo_wv_energy(self): 121 | """Test the energy property of the pseudo WV representation.""" 122 | signal, _ = fmsin(128) 123 | signal = signal / 128.0 124 | tfr, _, _ = cohen.PseudoWignerVilleDistribution(signal).run() 125 | x = np.sum(np.sum(tfr)) 126 | y = np.sum(np.abs(signal) ** 2) * 128 127 | self.assertAlmostEqual(x, y, places=3) 128 | 129 | def test_pseudo_wv_reality(self): 130 | """Test the reality property of the pseudo WV representation.""" 131 | signal, _ = fmsin(128) 132 | tfr, _, _ = cohen.PseudoWignerVilleDistribution(signal).run() 133 | self.assertTrue(np.all(np.isreal(tfr))) 134 | 135 | 136 | if __name__ == '__main__': 137 | unittest.main() 138 | -------------------------------------------------------------------------------- /tftb/processing/tests/test_freq_domain.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """Tests for frequency domain processing functions.""" 10 | 11 | 12 | import unittest 13 | import numpy as np 14 | from tftb.processing import freq_domain as fproc 15 | from tftb.generators import frequency_modulated as fm 16 | from tftb.generators import amplitude_modulated as am 17 | from tftb.tests.test_base import TestBase 18 | 19 | 20 | class TestFrequencyDomainProcessing(TestBase): 21 | 22 | def test_instfreq(self): 23 | """Test instantaneous frequency calculation.""" 24 | signal, _ = fm.fmlin(128, 0.05, 0.3, 50) 25 | ifreq = fproc.inst_freq(signal)[0] 26 | self.assertAlmostEqual(ifreq.min(), 0.05, places=2) 27 | self.assertAlmostEqual(ifreq.max(), 0.3, places=2) 28 | self.assert_is_linear(ifreq) 29 | 30 | def test_locfreq(self): 31 | """Test calculation of localized frequency characteristics.""" 32 | signal, _ = fm.fmlin(128, 0.05, 0.3, 50) 33 | input_avg_freq = (0.05 + 0.3) / 2.0 34 | avg_norm_freq, bandwidth = fproc.locfreq(signal) 35 | self.assertAlmostEqual(avg_norm_freq, input_avg_freq, places=2) 36 | 37 | def test_group_delay(self): 38 | """Test Group delay calculation.""" 39 | n_points = 128 40 | signal = am.amgauss(n_points, 64, 30) * fm.fmlin(n_points, 0.1, 0.4)[0] 41 | fnorm = np.arange(0.1, 0.38, step=0.04) 42 | grp_dlay = fproc.group_delay(signal, fnorm) 43 | self.assert_is_linear(grp_dlay, 0) 44 | 45 | 46 | if __name__ == '__main__': 47 | unittest.main() 48 | -------------------------------------------------------------------------------- /tftb/processing/tests/test_linear.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Tests for tftb.processing.linear 11 | """ 12 | 13 | from tftb.processing import linear 14 | from tftb.generators import fmlin 15 | from tftb.tests.test_base import TestBase 16 | import numpy as np 17 | import unittest 18 | 19 | 20 | class TestLinear(TestBase): 21 | 22 | def test_stft_linearity(self): 23 | """Test the linearity property of the Fourier transform.""" 24 | x = fmlin(128, 0.0, 0.2)[0] 25 | y = fmlin(128, 0.3, 0.5)[0] 26 | h = x + y 27 | tfr, _, _ = linear.ShortTimeFourierTransform(h).run() 28 | tfrx, _, _ = linear.ShortTimeFourierTransform(x).run() 29 | tfry, _, _ = linear.ShortTimeFourierTransform(y).run() 30 | np.testing.assert_allclose(tfr, tfrx + tfry) 31 | 32 | @unittest.skip("Known failure.") 33 | def test_stft_translation(self): 34 | """Test the time-shift property of the Fourier transform.""" 35 | x = fmlin(128, 0.0, 0.2)[0] 36 | tfrx, _, freqs = linear.ShortTimeFourierTransform(x).run() 37 | x_shifted = np.roll(x, 64) 38 | tfrxs, _, _ = linear.ShortTimeFourierTransform(x_shifted).run() 39 | f_mul = np.exp(-1j * 2 * np.pi * 64 * freqs) 40 | np.testing.assert_allclose(tfrxs, f_mul * tfrx) 41 | 42 | def test_stft_modulation(self): 43 | """Test the modulation / frequency shifting property of STFT.""" 44 | x = fmlin(128, 0.0, 0.2)[0] 45 | tfrx, _, _ = linear.ShortTimeFourierTransform(x).run() 46 | f_0 = 0.3 47 | h = np.exp(1j * 2 * np.pi * f_0 * np.arange(128)) * x 48 | tfrh, _, _ = linear.ShortTimeFourierTransform(h).run() 49 | tfrx_shifted = np.roll(tfrx, int(np.ceil(128 * 0.3)), axis=0) 50 | self.assert_tfr_equal(tfrx_shifted, tfrh, tol=0.95) 51 | 52 | @unittest.skip("Known failure") 53 | def test_stft_conjugation(self): 54 | x = fmlin(128, 0, 0.2)[0] 55 | h = np.conjugate(x) 56 | lhs, _, _ = linear.ShortTimeFourierTransform(h).run() 57 | rhs, _, _ = linear.ShortTimeFourierTransform(x[::-1]).run() 58 | rhs = np.conjugate(rhs) 59 | np.testing.assert_allclose(lhs, rhs) 60 | 61 | 62 | if __name__ == '__main__': 63 | unittest.main() 64 | -------------------------------------------------------------------------------- /tftb/processing/tests/test_postprocessing.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Tests for tftb.processing.postprocessing 11 | """ 12 | 13 | from tftb.tests.test_base import TestBase 14 | from tftb.generators import atoms, fmlin 15 | from tftb.processing import WignerVilleDistribution 16 | from tftb.processing import postprocessing as pproc 17 | from skimage.transform import hough_line_peaks, hough_line 18 | import numpy as np 19 | import unittest 20 | 21 | 22 | class TestPostprocessing(TestBase): 23 | 24 | def test_renyi_information(self): 25 | """Check if Renyi entropy computation is correct.""" 26 | sig = atoms(128, np.array([[64., 0.25, 20., 1.]])) 27 | tfr, _, _ = WignerVilleDistribution(sig).run() 28 | R1 = pproc.renyi_information(tfr) 29 | sig = atoms(128, np.array([[32., 0.25, 20., 1.], 30 | [96., 0.25, 20., 1.]])) 31 | tfr, _, _ = WignerVilleDistribution(sig).run() 32 | R2 = pproc.renyi_information(tfr) 33 | self.assertAlmostEqual(R2 - R1, 0.98, places=1) 34 | 35 | def test_ideal_tfr(self): 36 | """Test if the ideal TFR can be found using the instantaneous frequency 37 | laws.""" 38 | _, iflaw1 = fmlin(128, 0.0, 0.2) 39 | _, iflaw2 = fmlin(128, 0.3, 0.5) 40 | iflaws = np.c_[iflaw1, iflaw2].T 41 | tfr, _, _ = pproc.ideal_tfr(iflaws) 42 | tfr[tfr == 1] = 255 43 | tfr = tfr.astype(np.uint8) 44 | hspace, angles, dists = hough_line(tfr) 45 | for x in hough_line_peaks(hspace, angles, dists): 46 | self.assertEqual(len(x), 2) 47 | 48 | 49 | if __name__ == '__main__': 50 | unittest.main() 51 | -------------------------------------------------------------------------------- /tftb/processing/tests/test_time_domain.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | 11 | """ 12 | 13 | import unittest 14 | from tftb.processing import time_domain as tmd 15 | from tftb.generators import amplitude_modulated as am 16 | from tftb.tests.test_base import TestBase 17 | 18 | 19 | class TestTimeDomainProcessors(TestBase): 20 | 21 | def test_loctime(self): 22 | """Test computation of localized time characteristics.""" 23 | signal = am.amgauss(160, 80, 50) 24 | tm, T = tmd.loctime(signal) 25 | self.assertAlmostEqual(tm, 79, places=6) 26 | self.assertAlmostEqual(T, 50, places=4) 27 | 28 | 29 | if __name__ == '__main__': 30 | unittest.main() 31 | -------------------------------------------------------------------------------- /tftb/processing/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Tests for tftb.processing.utils 11 | """ 12 | 13 | import unittest 14 | import numpy as np 15 | from tftb.processing import utils 16 | 17 | 18 | class TestUtils(unittest.TestCase): 19 | 20 | def test_derive_window(self): 21 | """Test derivative of window function.""" 22 | from scipy.signal.windows import gaussian 23 | g = gaussian(129, 10) 24 | dwindow = utils.derive_window(g) 25 | self.assertEqual(dwindow[64], 0) 26 | self.assertTrue(np.all(dwindow[:64] >= 0)) 27 | self.assertTrue(np.all(dwindow[64:] <= 0)) 28 | 29 | 30 | if __name__ == '__main__': 31 | unittest.main() 32 | -------------------------------------------------------------------------------- /tftb/processing/time_domain.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def loctime(sig): 5 | """ 6 | Compute the time localization characteristics. 7 | 8 | :param sig: input signal 9 | :type sig: numpy.ndarray 10 | :return: Average time center and time spreading 11 | :rtype: tuple 12 | :Example: 13 | >>> from tftb.generators import amgauss 14 | >>> x = amgauss(160, 80.0, 50.0) 15 | >>> tm, T = loctime(x) 16 | >>> print("%.2f" % tm) 17 | 79.00 18 | >>> print("%.2f" % T) 19 | 50.00 20 | """ 21 | if sig.ndim > 2: 22 | if 1 not in sig.shape: 23 | raise TypeError 24 | else: 25 | sig = sig.ravel() 26 | sig2 = np.abs(sig**2) 27 | sig2 = sig2 / sig2.mean() 28 | t = np.arange(len(sig)) 29 | tm = np.mean(t * sig2) 30 | T = 2 * np.sqrt(np.pi * np.mean(((t - tm)**2) * sig2)) 31 | return tm, T 32 | -------------------------------------------------------------------------------- /tftb/processing/utils.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """Miscellaneous processing utilities.""" 10 | 11 | import numpy as np 12 | 13 | 14 | def get_spectrum(signal): 15 | return np.fft.fftshift(np.abs(np.fft.fft(signal)) ** 2) 16 | 17 | 18 | def integrate_2d(mat, x=None, y=None): 19 | """integrate_2d 20 | 21 | :param mat: 22 | :param x: 23 | :param y: 24 | :type mat: 25 | :type x: 26 | :type y: 27 | :return: 28 | :rtype: 29 | :Example: 30 | >>> from __future__ import print_function 31 | >>> from tftb.generators import altes 32 | >>> from tftb.processing import Scalogram 33 | >>> x = altes(256, 0.1, 0.45, 10000) 34 | >>> tfr, t, f, _ = Scalogram(x).run() 35 | >>> print("%.3f" % integrate_2d(tfr, t, f)) 36 | 2.000 37 | """ 38 | m, n = mat.shape 39 | if x is None: 40 | x = np.arange(n) 41 | if y is None: 42 | y = np.arange(m) 43 | 44 | mat = (mat.sum(1) - mat[:, 0] / 2.0 - mat[:, n - 1] / 2.0) * (x[1] - x[0]) 45 | dmat = mat[:(m - 1)] + mat[1:] 46 | dy = (y[1:] - y[:(m - 1)]) / 2.0 47 | return np.sum(dmat * dy) 48 | 49 | 50 | def derive_window(window): 51 | """Calculate derivative of a window function. 52 | 53 | :param window: Window function to be differentiated. This is expected to be 54 | a standard window function with an odd length. 55 | :type window: array-like 56 | :return: Derivative of the input window 57 | :rtype: array-like 58 | :Example: 59 | >>> from scipy.signal import hann 60 | >>> import matplotlib.pyplot as plt #doctest: +SKIP 61 | >>> window = hann(210) 62 | >>> derivation = derive_window(window) 63 | >>> plt.subplot(211), plt.plot(window) #doctest: +SKIP 64 | >>> plt.subplot(212), plt.plot(derivation) #doctest: +SKIP 65 | 66 | .. plot:: docstring_plots/processing/utils/derive_window.py 67 | """ 68 | lh = (window.shape[0] - 1) / 2.0 69 | step_height = (window[0] + window[-1]) / 2.0 70 | ramp = (window[0] - window[-1]) / (window.shape[0] - 1) 71 | base = np.arange(-lh, lh + 1) 72 | base = window - step_height - ramp * base 73 | base = np.hstack((np.array([0]), base, np.array([0]))) 74 | dw = (base[2:(window.shape[0] + 2)] - base[:window.shape[0]]) / 2.0 + ramp 75 | dw[0] += step_height 76 | dw[-1] -= step_height 77 | return dw 78 | 79 | 80 | if __name__ == '__main__': 81 | x = np.arange(1, 16).reshape(5, 3) 82 | print(integrate_2d(x)) 83 | -------------------------------------------------------------------------------- /tftb/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-signal/tftb/18c784d9a8539436165aff6f6ca5f100b116ee8b/tftb/tests/__init__.py -------------------------------------------------------------------------------- /tftb/tests/test_base.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """Base class for tests.""" 10 | 11 | import unittest 12 | import numpy as np 13 | from numpy import angle 14 | from tftb.utils import is_linear 15 | from skimage.metrics import structural_similarity 16 | 17 | 18 | class TestBase(unittest.TestCase): 19 | 20 | def assert_is_linear(self, signal, decimals=5): 21 | """Assert that the signal is linear.""" 22 | self.assertTrue(is_linear(signal, decimals=decimals)) 23 | 24 | def assert_is_analytic(self, signal, amlaw=None): 25 | """Assert that signal is analytic.""" 26 | omega = angle(signal) 27 | if amlaw is not None: 28 | recons = np.exp(1j * omega) * amlaw 29 | else: 30 | recons = np.exp(1j * omega) 31 | real_identical = np.allclose(np.real(recons), np.real(signal)) 32 | imag_identical = np.allclose(np.imag(recons), np.imag(signal)) 33 | if not (imag_identical and real_identical): 34 | raise AssertionError("Signal is not analytic.") 35 | 36 | def assert_is_concave(self, signal): 37 | second_derivative = np.diff(np.diff(signal)) 38 | if not np.all(second_derivative < 0): 39 | raise AssertionError("Signal is not concave.") 40 | 41 | def assert_is_convex(self, signal): 42 | second_derivative = np.diff(np.diff(signal)) 43 | if not np.all(second_derivative > 0): 44 | raise AssertionError("Signal is not convex.") 45 | 46 | def assert_is_monotonic_increasing(self, signal): 47 | derivative = np.diff(signal) 48 | if not np.all(derivative >= 0): 49 | raise AssertionError("Signal is not monotonically increasing.") 50 | 51 | def assert_is_monotonic_decreasing(self, signal): 52 | derivative = np.diff(signal) 53 | if not np.all(derivative <= 0): 54 | raise AssertionError("Signal is not monotonically decreasing.") 55 | 56 | def assert_is_hermitian(self, x): 57 | """Assert that the input is a Hermitian matrix.""" 58 | conj_trans = np.conj(x).T 59 | np.testing.assert_allclose(x, conj_trans) 60 | 61 | def assert_tfr_equal(self, x, y, sqmod=True, threshold=0.05, tol=0.99): 62 | """Assert that TFRs x and y are qualitatively equivalent.""" 63 | if sqmod: 64 | x = np.abs(x) ** 2 65 | y = np.abs(y) ** 2 66 | x_thresh = np.amax(x) * threshold 67 | x[x <= x_thresh] = 0.0 68 | y_thresh = np.amax(y) * threshold 69 | y[y <= y_thresh] = 0.0 70 | x = np.ascontiguousarray(x) 71 | y = np.ascontiguousarray(y) 72 | similarity = structural_similarity(x, y, data_range=np.amax(x) - np.amin(x)) 73 | self.assertTrue(similarity >= tol) 74 | -------------------------------------------------------------------------------- /tftb/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 jaidev 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Tests for tftb.utils 11 | """ 12 | 13 | import unittest 14 | import numpy as np 15 | from tftb import utils 16 | 17 | 18 | class TestUtils(unittest.TestCase): 19 | 20 | def test_is_linear(self): 21 | """Test the is_linear function.""" 22 | x = np.arange(10) 23 | self.assertTrue(utils.is_linear(x)) 24 | x = np.sin(x) 25 | self.assertFalse(utils.is_linear(x)) 26 | 27 | def test_nextpow2(self): 28 | """Test the nextpow2 function.""" 29 | self.assertEqual(utils.nextpow2(2), 1) 30 | self.assertEqual(utils.nextpow2(17), 5) 31 | with self.assertRaises(ValueError): 32 | utils.nextpow2(-3) 33 | 34 | def test_divider(self): 35 | """Test the divider function.""" 36 | self.assertSequenceEqual(utils.divider(4), (2, 2)) 37 | self.assertSequenceEqual(utils.divider(17), (1, 17)) 38 | self.assertSequenceEqual(utils.divider(60), (6, 10)) 39 | x = np.arange(1, 101) 40 | lowers = np.zeros(x.shape) 41 | uppers = np.zeros(x.shape) 42 | for i, num in enumerate(x): 43 | a, b = utils.divider(num) 44 | lowers[i] = a 45 | uppers[i] = b 46 | perfect_squares = np.arange(1, 11) ** 2 47 | np.testing.assert_allclose(perfect_squares, x[lowers == uppers]) 48 | 49 | def test_nearest_odd(self): 50 | """Test the nearest_odd function.""" 51 | self.assertEqual(utils.nearest_odd(0), 1) 52 | self.assertEqual(utils.nearest_odd(2), 3) 53 | self.assertEqual(utils.nearest_odd(-0.00001), -1) 54 | 55 | def test_modulo(self): 56 | """Test the modulo function.""" 57 | x = np.arange(1, 11) 58 | np.testing.assert_allclose(utils.modulo(x, 1), np.ones(x.shape)) 59 | np.testing.assert_allclose(utils.modulo(x, 2), 60 | np.array([1, 2, 1, 2, 1, 2, 1, 2, 1, 2])) 61 | np.testing.assert_allclose(utils.modulo(x, 3), 62 | np.array([1, 2, 3, 1, 2, 3, 1, 2, 3, 1])) 63 | np.testing.assert_allclose(utils.modulo(x, 4), 64 | np.array([1, 2, 3, 4, 1, 2, 3, 4, 1, 2])) 65 | np.testing.assert_allclose(utils.modulo(x, 5), 66 | np.array([1, 2, 3, 4, 5, 1, 2, 3, 4, 5])) 67 | 68 | 69 | if __name__ == '__main__': 70 | unittest.main() 71 | -------------------------------------------------------------------------------- /tftb/utils.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | """Miscellaneous utilities.""" 4 | 5 | import math 6 | import numpy as np 7 | 8 | 9 | def is_linear(x, decimals=5): 10 | """ 11 | Check if an array is linear. 12 | 13 | :param x: Array to be checked for linearity. 14 | :param decimals: decimal places upto which the derivative of the array 15 | should be rounded off (default=5) 16 | :type x: numpy.ndarray 17 | :type decimals: int 18 | :return: If the array is linear 19 | :rtype: boolean 20 | :Example: 21 | >>> import numpy as np 22 | >>> x = np.linspace(0, 2 * np.pi, 100) 23 | >>> is_linear(x) 24 | True 25 | >>> is_linear(np.sin(x)) 26 | False 27 | """ 28 | derivative = np.diff(x) 29 | derivative = np.around(derivative, decimals) 30 | return np.unique(derivative).shape[0] == 1 31 | 32 | 33 | def izak(x): 34 | """Inverse Zak transform.""" 35 | if x.ndim == 2: 36 | n, m = x.shape 37 | else: 38 | n, m = x.shape[0], 1 39 | sig = np.zeros((n * m, ), dtype=complex) 40 | for im in range(m): 41 | sig[im + np.arange(n) * m] = np.sqrt(n) * np.fft.ifft(x[:, im], axis=0) 42 | return sig 43 | 44 | 45 | def nextpow2(n): 46 | """ 47 | Compute the integer exponent of the next higher power of 2. 48 | 49 | :param n: Number whose next higest power of 2 needs to be computed. 50 | :type n: int, np.ndarray 51 | :rtype: int, np.ndarray 52 | :Example: 53 | >>> from __future__ import print_function 54 | >>> import numpy as np 55 | >>> x = np.arange(1, 9) 56 | >>> print(nextpow2(x)) 57 | [ 0. 1. 2. 2. 3. 3. 3. 3.] 58 | """ 59 | return math.ceil(math.log2(n)) 60 | 61 | 62 | def divider(N): 63 | """ 64 | Compute two factors of N such that they are as close as possible to sqrt(N). 65 | 66 | :param N: Number to be divided. 67 | :type N: int 68 | :return: A tuple of two integers such that their product is `N` and they 69 | are the closest possible to :math:`\sqrt(N)` # NOQA: W605 70 | :rtype: tuple(int) 71 | :Example: 72 | >>> from __future__ import print_function 73 | >>> print(divider(256)) 74 | (16, 16) 75 | >>> print(divider(10)) 76 | (2, 5) 77 | >>> print(divider(101)) 78 | (1, 101) 79 | """ 80 | s = math.sqrt(N) 81 | if s % 1 == 0: 82 | s = int(s) 83 | return s, s 84 | s = math.ceil(s) 85 | for i in range(s, 0, -1): 86 | factor = N // i 87 | if N % i == 0: 88 | break 89 | return i, factor 90 | 91 | 92 | def nearest_odd(N): 93 | """ 94 | Get the nearest odd number for each value of N. 95 | 96 | :param N: int / sequence of ints 97 | :return: int / sequence of ints 98 | :Example: 99 | >>> from __future__ import print_function 100 | >>> print(nearest_odd(range(1, 11))) 101 | [ 1. 3. 3. 5. 5. 7. 7. 9. 9. 11.] 102 | >>> nearest_odd(0) 103 | 1 104 | >>> nearest_odd(3) 105 | 3.0 106 | """ 107 | if isinstance(N, (list, np.ndarray)): 108 | y = np.floor(N) 109 | y[np.remainder(y, 2) == 0] += 1 110 | return y.astype(int) 111 | if N % 2 == 0: 112 | return N + 1 113 | elif math.floor(N) % 2 == 0: 114 | return math.ceil(N) 115 | elif math.floor(N) % 2 != 0: 116 | return math.floor(N) 117 | return N 118 | 119 | 120 | def modulo(x, N): 121 | """ 122 | Compute the congruence of each element of x modulo N. 123 | 124 | :type x: array-like 125 | :type N: int 126 | :return: array-like 127 | :Example: 128 | >>> from __future__ import print_function 129 | >>> print(modulo(range(1, 11), 2)) 130 | [1 2 1 2 1 2 1 2 1 2] 131 | """ 132 | if any(np.isreal(x)): 133 | y = np.mod(x, N) 134 | y[y == 0] = N 135 | else: 136 | y = np.mod(np.real(x), N) + 1j * np.mod(np.imag(x), N) 137 | return y 138 | --------------------------------------------------------------------------------