# Source code for obspy.signal.cpxtrace

    # -*- coding: utf-8 -*-
    # -------------------------------------------------------------------
    # Filename: cpxtrace.py
    #   Author: Conny Hammer
    #    Email: conny.hammer@geo.uni-potsdam.de
    #
    # Copyright (C) 2008-2012 Conny Hammer
    # ------------------------------------------------------------------
    """
    Complex Trace Analysis

    :copyright:
        The ObsPy Development Team (devs@obspy.org)
    :license:
        GNU Lesser General Public License, Version 3
        (https://www.gnu.org/copyleft/lesser.html)
    """
    from __future__ import (absolute_import, division, print_function,
                            unicode_literals)
    from future.builtins import *  # NOQA

    import numpy as np
    from scipy import signal
    from scipy.integrate import cumtrapz

    from . import util

    def envelope(data):
        """
        Envelope of a signal.

        Computes the envelope of the given data which can be windowed or
        not. The envelope is determined by the absolute value of the analytic
        signal of the given data.

        If data are windowed the analytic signal and the envelope of each
        window is returned.

        :type data: :class:`~numpy.ndarray`
        :param data: Data to make envelope of.
        :return: **A_cpx, A_abs** - Analytic signal of input data, Envelope of
            input data.
        """
        nfft = util.next_pow_2(data.shape[-1])
        a_cpx = np.zeros((data.shape), dtype=np.complex64)
        a_abs = np.zeros((data.shape), dtype=np.float64)
        if len(data.shape) > 1:
            i = 0
            for row in data:
                a_cpx[i, :] = signal.hilbert(row, nfft)
                a_abs[i, :] = abs(signal.hilbert(row, nfft))
                i = i + 1
        else:
            a_cpx = signal.hilbert(data, nfft)
            a_abs = abs(signal.hilbert(data, nfft))
        return a_cpx, a_abs

    def normalized_envelope(data, fs, smoothie, fk):
        """
        Normalized envelope of a signal.

        Computes the normalized envelope of the given data which can be windowed
        or not. In order to obtain a normalized measure of the signal envelope
        the instantaneous bandwidth of the smoothed envelope is normalized by the
        Nyquist frequency and is integrated afterwards.

        The time derivative of the normalized envelope is returned if input data
        are windowed only.

        :type data: :class:`~numpy.ndarray`
        :param data: Data to make normalized envelope of.
        :param fs: Sampling frequency.
        :param smoothie: Window length for moving average.
        :param fk: Coefficients for calculating time derivatives
            (calculated via central difference).
        :return: **Anorm[, dAnorm]** - Normalized envelope of input data, Time
            derivative of normalized envelope (windowed only).
        """
        x = envelope(data)
        fs = float(fs)
        if len(x[1].shape) > 1:
            i = 0
            anorm = np.zeros(x[1].shape[0], dtype=np.float64)
            for row in x[1]:
                a_win_smooth = util.smooth(row, int(np.floor(len(row) / 3)))
                # Differentiation of original signal, dA/dt
                # Better, because faster, calculation of A_win_add
                a_win_add = np.hstack(([a_win_smooth[0]] * (np.size(fk) // 2),
                                       a_win_smooth,
                                       [a_win_smooth[np.size(a_win_smooth) - 1]] *
                                       (np.size(fk) // 2)))
                t = signal.lfilter(fk, 1, a_win_add)
                # correct start and end values of time derivative
                t = t[np.size(fk) - 1:np.size(t)]
                a_win_smooth[a_win_smooth < 1] = 1
                # (dA/dt) / 2*PI*smooth(A)*fs/2
                t_ = t / (2. * np.pi * (a_win_smooth) * (fs / 2.0))
                # Integral within window
                t_ = cumtrapz(t_, dx=(1. / fs))
                t_ = np.concatenate((t_[0:1], t_))
                anorm[i] = ((np.exp(np.mean(t_))) - 1) * 100
                i = i + 1
            # faster alternative to calculate Anorm_add
            anorm_add = np.hstack(
                ([anorm[0]] * (np.size(fk) // 2), anorm,
                 [anorm[np.size(anorm) - 1]] * (np.size(fk) // 2)))
            danorm = signal.lfilter(fk, 1, anorm_add)
            # correct start and end values of time derivative
            danorm = danorm[np.size(fk) - 1:np.size(danorm)]
            # dAnorm = dAnorm[np.size(fk) // 2:
            #                 (np.size(dAnorm) - np.size(fk) // 2)]
            return anorm, danorm
        else:
            anorm = np.zeros(1, dtype=np.float64)
            a_win_smooth = util.smooth(x[1], smoothie)
            # Differentiation of original signal, dA/dt
            # Better, because faster, calculation of A_win_add
            a_win_add = np.hstack(
                ([a_win_smooth[0]] * (np.size(fk) // 2),
                 a_win_smooth, [a_win_smooth[np.size(a_win_smooth) - 1]] *
                 (np.size(fk) // 2)))
            t = signal.lfilter(fk, 1, a_win_add)
            # correct start and end values of time derivative
            t = t[np.size(fk) - 1:np.size(t)]
            a_win_smooth[a_win_smooth < 1] = 1
            t_ = t / (2. * np.pi * (a_win_smooth) * (fs / 2.0))
            # Integral within window
            t_ = cumtrapz(t_, dx=(1.0 / fs))
            t_ = np.concatenate((t_[0:1], t_))
            anorm = ((np.exp(np.mean(t_))) - 1) * 100
            return anorm

    def centroid(data, fk):
        """
        Centroid time of a signal.

        Computes the centroid time of the given data which can be windowed or
        not. The centroid time is determined as the time in the processed
        window where 50 per cent of the area below the envelope is reached.

        The time derivative of the centroid time is returned if input data are
        windowed only.

        :type data: :class:`~numpy.ndarray`
        :param data: Data to determine centroid time of.
        :param fk: Coefficients for calculating time derivatives
            (calculated via central difference).
        :return: **centroid[, dcentroid]** - Centroid time input data, Time
            derivative of centroid time (windowed only).
        """
        x = envelope(data)
        if len(x[1].shape) > 1:
            centroid = np.zeros(x[1].shape[0], dtype=np.float64)
            i = 0
            for row in x[1]:
                # Integral within window
                half = 0.5 * sum(row)
                # Estimate energy centroid
                for k in range(2, np.size(row)):
                    t = sum(row[0:k])
                    if (t >= half):
                        frac = (half - (t - sum(row[0:k - 1]))) / \
                            (t - (t - sum(row[0:k - 1])))
                        centroid[i] = \
                            (float(k - 1) + float(frac)) / float(np.size(row))
                        break
                i = i + 1
            centroid_add = np.hstack(
                ([centroid[0]] * (np.size(fk) // 2),
                 centroid, [centroid[np.size(centroid) - 1]] *
                 (np.size(fk) // 2)))
            dcentroid = signal.lfilter(fk, 1, centroid_add)
            dcentroid = dcentroid[np.size(fk) - 1:np.size(dcentroid)]
            return centroid, dcentroid
        else:
            centroid = np.zeros(1, dtype=np.float64)
            # Integral within window
            half = 0.5 * sum(x[1])
            # Estimate energy centroid
            for k in range(2, np.size(x[1])):
                t = sum(x[1][0:k])
                if (t >= half):
                    frac = (half - (t - sum(x[1][0:k - 1]))) / \
                        (t - (t - sum(x[1][0:k - 1])))
                    centroid = (float(k) + float(frac)) / float(np.size(x[1]))
                    break
            return centroid

    def instantaneous_frequency(data, fs, fk):
        """
        Instantaneous frequency of a signal.

        Computes the instantaneous frequency of the given data which can be
        windowed or not. The instantaneous frequency is determined by the time
        derivative of the analytic signal of the input data.

        :type data: :class:`~numpy.ndarray`
        :param data: Data to determine instantaneous frequency of.
        :param fs: Sampling frequency.
        :param fk: Coefficients for calculating time derivatives
            (calculated via central difference).
        :return: **omega[, domega]** - Instantaneous frequency of input data, Time
            derivative of instantaneous frequency (windowed only).
        """
        x = envelope(data)
        if len(x[0].shape) > 1:
            omega = np.zeros(x[0].shape[0], dtype=np.float64)
            i = 0
            for row in x[0]:
                f = np.real(row)
                h = np.imag(row)
                # faster alternative to calculate f_add
                f_add = np.hstack(
                    ([f[0]] * (np.size(fk) // 2), f,
                     [f[np.size(f) - 1]] * (np.size(fk) // 2)))
                fd = signal.lfilter(fk, 1, f_add)
                # correct start and end values of time derivative
                fd = fd[np.size(fk) - 1:np.size(fd)]
                # faster alternative to calculate h_add
                h_add = np.hstack(
                    ([h[0]] * (np.size(fk) // 2), h,
                     [h[np.size(h) - 1]] * (np.size(fk) // 2)))
                hd = signal.lfilter(fk, 1, h_add)
                # correct start and end values of time derivative
                hd = hd[np.size(fk) - 1:np.size(hd)]
                omega_win = abs(((f * hd - fd * h) / (f * f + h * h)) *
                                fs / 2 / np.pi)
                omega[i] = np.median(omega_win)
                i = i + 1
            # faster alternative to calculate omega_add
            omega_add = np.hstack(
                ([omega[0]] * (np.size(fk) // 2), omega,
                 [omega[np.size(omega) - 1]] * (np.size(fk) // 2)))
            domega = signal.lfilter(fk, 1, omega_add)
            # correct start and end values of time derivative
            domega = domega[np.size(fk) - 1:np.size(domega)]
            return omega, domega
        else:
            omega = np.zeros(np.size(x[0]), dtype=np.float64)
            f = np.real(x[0])
            h = np.imag(x[0])
            # faster alternative to calculate f_add
            f_add = np.hstack(
                ([f[0]] * (np.size(fk) // 2), f,
                 [f[np.size(f) - 1]] * (np.size(fk) // 2)))
            fd = signal.lfilter(fk, 1, f_add)
            # correct start and end values of time derivative
            fd = fd[np.size(fk) - 1:np.size(fd)]
            # faster alternative to calculate h_add
            h_add = np.hstack(
                ([h[0]] * (np.size(fk) // 2), h,
                 [h[np.size(h) - 1]] * (np.size(fk) // 2)))
            hd = signal.lfilter(fk, 1, h_add)
            # correct start and end values of time derivative
            hd = hd[np.size(fk) - 1:np.size(hd)]
            omega = abs(((f * hd - fd * h) / (f * f + h * h)) * fs / 2 / np.pi)
            return omega

    def instantaneous_bandwidth(data, fs, fk):
        """
        Instantaneous bandwidth of a signal.

        Computes the instantaneous bandwidth of the given data which can be
        windowed or not. The instantaneous bandwidth is determined by the time
        derivative of the envelope normalized by the envelope of the input data.

        :type data: :class:`~numpy.ndarray`
        :param data: Data to determine instantaneous bandwidth of.
        :param fs: Sampling frequency.
        :param fk: Filter coefficients for computing time derivative.
        :return: **sigma[, dsigma]** - Instantaneous bandwidth of input data, Time
            derivative of instantaneous bandwidth (windowed only).
        """
        x = envelope(data)
        if len(x[1].shape) > 1:
            sigma = np.zeros(x[1].shape[0], dtype=np.float64)
            i = 0
            for row in x[1]:
                # faster alternative to calculate A_win_add
                a_win_add = np.hstack(
                    ([row[0]] * (np.size(fk) // 2), row,
                     [row[np.size(row) - 1]] * (np.size(fk) // 2)))
                t = signal.lfilter(fk, 1, a_win_add)
                # t = t[np.size(fk) // 2:(np.size(t) - np.size(fk) // 2)]
                # correct start and end values
                t = t[np.size(fk) - 1:np.size(t)]
                sigma_win = abs((t * fs) / (row * 2 * np.pi))
                sigma[i] = np.median(sigma_win)
                i = i + 1
            # faster alternative to calculate sigma_add
            sigma_add = np.hstack(
                ([sigma[0]] * (np.size(fk) // 2), sigma,
                 [sigma[np.size(sigma) - 1]] * (np.size(fk) // 2)))
            dsigma = signal.lfilter(fk, 1, sigma_add)
            # dsigma = dsigma[np.size(fk) // 2:
            #                 (np.size(dsigma) - np.size(fk) // 2)]
            # correct start and end values
            dsigma = dsigma[np.size(fk) - 1:np.size(dsigma)]
            return sigma, dsigma
        else:
            row = x[1]
            # faster alternative to calculate A_win_add
            a_win_add = np.hstack(
                ([row[0]] * (np.size(fk) // 2), row,
                 [row[np.size(row) - 1]] * (np.size(fk) // 2)))
            t = signal.lfilter(fk, 1, a_win_add)
            # correct start and end values
            t = t[np.size(fk) - 1:np.size(t)]
            sigma = abs((t * fs) / (x[1] * 2 * np.pi))
            return sigma

### Thank you!

We would like to thank our contributors, whose efforts make this software what it is. These people have helped by writing code and documentation, and by testing. They have created and maintained this product, its associated libraries and applications, our build tools and our web sites.

#### Contributors

- Ackerley, Nick
- Allgeyer, Sebastien
- Ammon, Charles J.
- Antunes, Emanuel
- Arnarsson, Ólafur St.
- Bank, Markus
- Barsch, Robert
- Behr, Yannik
- Bernardi, Fabrizio
- Bernauer, Felix
- Bes de Berc, Maxime
- Beyreuther, Moritz
- Boltz, Shawn
- Bonaimé, Sébastien
- Carothers, Lloyd
- Chamberlain, Calum
- Chambers, Derrick
- Chen, Zhao
- Clark, Adam
- Danecek, Peter
- Egdorf, Sven
- Engels, Fabian
- Ermert, Laura
- Eulenfeld, Tom

- Fabbri, Tommaso
- Falco, Nicholas
- Fee, Jeremy
- Grellier, Clément
- Grunberg, Marc
- Hammer, Conny
- Heimann, Sebastian
- Heiniger, Lukas
- Hope, Gaute
- Igel, Heiner
- Inza, Adolfo
- Isken, Marius
- Ketchum, David
- Koymans, Mathijs
- Kremers, Simon
- Kress, Victor
- Krieger, Lars
- Krischer, Lion
- Kula, Damian
- Käufl, Paul
- Köhler, Andreas
- Lecocq, Thomas
- Leeman, John
- Legovini, Paride

- Lesage, Philippe
- Li, Yulin
- Lomax, Anthony
- Lopes, Rui L.
- MacCarthy, Jonathan
- Maggi, Alessia
- Martin, Henri
- Medlin, Andrew
- Megies, Tobias
- Meschede, Matthias
- Michelini, Alberto
- Miller, Nathaniel C.
- Morgenstern, Bernhard
- Nof, Ran Novitsky
- Panning, Mark P.
- Parker, Tom
- Pestourie, Romain
- Rapagnani, Giovanni
- Reyes, Celso
- Ringler, Adam
- Rothenhäusler, Nicolas
- Russo, Emiliano
- Sales de Andrade, Elliott

- Satriano, Claudio
- Saul, Joachim
- Scheingraber, Chris
- Schneider, Simon
- Sippl, Christian
- Snoke, Arthur
- Stange, Stefan
- Stähler, Simon C.
- Sullivan, Benjamin
- Tian, Dongdong
- Trabant, Chad
- Trani, Luca
- Uieda, Leonardo
- Walker, Andrew
- Walther, Marcus
- Wassermann, Joachim
- Williams, Mark C.
- Winkelman, Andrew
- Zaccarelli, Riccardo
- Zad, Seyed Kasra Hosseini
- Zhu, Lijun
- van Driel, Martin
- van Wijk, Kasper

#### Funds

ObsPy was partially funded by the

- German Science Foundation (DFG) via grant DFG IG 16/9-1
- German Ministry for Education and Research (BMBF), GEOTECHNOLOGIEN grant 03G0646H.
- NERA project (Network of European Research Infrastructures for Earthquake Risk Assessment and Mitigation) under the European Community's Seventh Framework Programme (FP7/2007-2013) grant agreement n° 262330
- Leibniz Institute for Applied Geophysics (LIAG)
- VERCE EU-FP7 project (no. 283543)