Example #1
0
def adsrc_tf_phase_misfit(t, data, synthetic, dt_new, width, threshold,
        axis=None, colorbar_axis=None):
    """
    :rtype: dictionary
    :returns: Return a dictionary with three keys:
        * adjoint_source: The calculated adjoint source as a numpy array
        * misfit: The misfit value
        * messages: A list of strings giving additional hints to what happened
            in the calculation.
    """
    messages = []
    # Compute time-frequency representation via cross-correlation
    tau_cc, nu_cc, tf_cc = time_frequency.time_frequency_cc_difference(
        t, data, synthetic, dt_new, width, threshold)
    # Compute the time-frequency representation of the synthetic
    tau, nu, tf_synth = time_frequency.time_frequency_transform(t,
        synthetic, dt_new, width, threshold)

    # 2D interpolation. Use a two step interpolation for the real and the
    # imaginary parts.
    tf_cc_interp = RectBivariateSpline(tau_cc[0], nu_cc[:, 0], tf_cc.real,
        kx=1, ky=1, s=0)(tau[0], nu[:, 0])
    tf_cc_interp = np.require(tf_cc_interp, dtype="complex128")
    tf_cc_interp.imag = RectBivariateSpline(tau_cc[0], nu_cc[:, 0], tf_cc.imag,
        kx=1, ky=1, s=0)(tau[0], nu[:, 0])
    tf_cc = tf_cc_interp

    # Make window functionality
    # noise taper
    m = np.abs(tf_cc).max() / 10.0
    weight = 1.0 - np.exp(-(np.abs(tf_cc) ** 2) / (m ** 2))
    nu_t = nu.transpose()
    # high-pass filter
    weight *= (1.0 - np.exp((-nu_t ** 2) / (0.002 ** 2)))
    nu_t_large = np.zeros(nu_t.shape)
    nu_t_small = np.zeros(nu_t.shape)
    thres = (nu_t <= 0.005)
    nu_t_large[np.invert(thres)] = 1.0
    nu_t_small[thres] = 1.0
    # low-pass filter
    weight *= (np.exp(-(nu_t - 0.005) ** 4 / 0.005 ** 4) *
        nu_t_large + nu_t_small)
    # normalisation
    weight /= weight.max()

    # Compute the phase difference.
    DP = np.imag(np.log(eps + tf_cc / (eps + np.abs(tf_cc))))

    # Attempt to detect phase jumps by taking the derivatives in time and
    # frequency direction. 0.7 is an emperical value.
    test_field = weight * DP / np.abs(weight * DP).max()
    criterion_1 = np.abs(np.diff(test_field, axis=0)).max()
    criterion_2 = np.abs(np.diff(test_field, axis=1)).max()
    criterion = max(criterion_1, criterion_2)
    if criterion > 0.7:
        warning = "Possible phase jump detected"
        warnings.warn(warning)
        messages.append(warning)

    # Compute the phase misfit
    dnu = nu[1, 0] - nu[0, 0]
    phase_misfit = np.sqrt(np.sum(weight ** 2 * DP ** 2) * dt_new * dnu)

    # Sanity check. Should not occur.
    if np.isnan(phase_misfit):
        msg = "The phase misfit is NaN."
        raise Exception(msg)

    # Make kernel for the inverse tf transform
    idp = weight * weight * DP * tf_synth / (eps + np.abs(tf_synth) *
        np.abs(tf_synth))

    # Invert tf transform and make adjoint source
    ad_src, it, I = time_frequency.itfa(tau, nu, idp, width, threshold)

    # Interpolate to original time axis
    current_time = tau[0, :]
    new_time = t[t <= current_time.max()]
    ad_src = interp1d(current_time, np.imag(ad_src), kind=2)(new_time)
    if len(t) > len(new_time):
        ad_src = np.concatenate([ad_src, np.zeros(len(t) - len(new_time))])

    # Divide by the misfit.
    ad_src /= (phase_misfit + eps)
    ad_src = np.diff(ad_src) / (t[1] - t[0])

    # Reverse time and add a leading zero so the adjoint source has the same
    # length as the input time series.
    ad_src = ad_src[::-1]
    ad_src = np.concatenate([[0.0], ad_src])

    # Plot if required.
    if axis:
        import matplotlib.cm as cm
        import matplotlib.pyplot as plt
        weighted_phase_difference = (DP * weight).transpose()
        abs_diff = np.abs(weighted_phase_difference)
        max_val = abs_diff.max()
        mappable = axis.pcolormesh(tau, nu,
            weighted_phase_difference, vmin=-max_val, vmax=max_val,
            cmap=cm.RdBu_r)
        axis.set_xlabel("Seconds since event")
        axis.set_ylabel("TF Phase Misfit: Frequency [Hz]")

        # Smart scaling for the frequency axis.
        temp = abs_diff.max(axis=1) * (nu[1] - nu[0])
        ymax = len(temp[temp > temp.max() / 1000.0])
        ymax *= nu[1, 0] - nu[0, 0]
        ymax *= 2
        axis.set_ylim(0, ymax)

        if colorbar_axis:
            cm = plt.gcf().colorbar(mappable, cax=colorbar_axis)
        else:
            cm = plt.gcf().colorbar(mappable, ax=axis)
        cm.set_label("Phase difference in radian")
        axis.set_title("Weighted phase difference")

        ax2 = axis.twinx()
        ax2.plot(t, data, color="black", alpha=1.0)
        ax2.plot(t, synthetic, color="red", alpha=1.0)
        min_value = min(data.min(), synthetic.min())
        max_value = max(data.max(), synthetic.max())
        value_range = max_value - min_value
        axis.twin_axis = ax2
        ax2.set_ylim(min_value - value_range, max_value + 0.05 * value_range)
        ax2.set_ylabel("Waveforms: Amplitude [m/s]")
        axis.set_xlim(0, tau[:, -1][-1])
        ax2.set_xlim(0, tau[:, -1][-1])

        text = "Misfit: %.4f" % phase_misfit
        axis.text(x=0.99, y=0.02, s=text, transform=axis.transAxes,
            bbox=dict(facecolor='orange', alpha=0.8),
            verticalalignment="bottom",
            horizontalalignment="right")

        if messages:
            message = "\n".join(messages)
            axis.text(x=0.99, y=0.98, s=message, transform=axis.transAxes,
                bbox=dict(facecolor='red', alpha=0.8),
                verticalalignment="top",
                horizontalalignment="right")

    ret_dict = {
        "adjoint_source": ad_src,
        "misfit": phase_misfit,
        "messages": messages}

    return ret_dict
Example #2
0
def adsrc_tf_phase_misfit(t,
                          data,
                          synthetic,
                          min_period,
                          max_period,
                          axis=None,
                          colorbar_axis=None):
    """
    :rtype: dictionary
    :returns: Return a dictionary with three keys:
        * adjoint_source: The calculated adjoint source as a numpy array
        * misfit: The misfit value
        * messages: A list of strings giving additional hints to what happened
            in the calculation.
    """
    messages = []

    # Compute time-frequency representations ----------------------------------

    # compute new time increments and Gaussian window width for the
    # time-frequency transforms
    dt_new = float(int(min_period / 3.0))
    width = 2.0 * min_period

    # Compute time-frequency representation of the cross-correlation
    tau_cc, nu_cc, tf_cc = time_frequency.time_frequency_cc_difference(
        t, data, synthetic, dt_new, width)
    # Compute the time-frequency representation of the synthetic
    tau, nu, tf_synth = time_frequency.time_frequency_transform(
        t, synthetic, dt_new, width)

    # 2D interpolation to bring the tf representation of the correlation on the
    # same grid as the tf representation of the synthetics. Uses a two-step
    # procedure for real and imaginary parts.
    tf_cc_interp = RectBivariateSpline(tau_cc[0],
                                       nu_cc[:, 0],
                                       tf_cc.real,
                                       kx=1,
                                       ky=1,
                                       s=0)(tau[0], nu[:, 0])
    tf_cc_interp = np.require(tf_cc_interp, dtype="complex128")
    tf_cc_interp.imag = RectBivariateSpline(tau_cc[0],
                                            nu_cc[:, 0],
                                            tf_cc.imag,
                                            kx=1,
                                            ky=1,
                                            s=0)(tau[0], nu[:, 0])
    tf_cc = tf_cc_interp

    # compute tf window and weighting function --------------------------------

    # noise taper: downweigh tf amplitudes that are very low
    m = np.abs(tf_cc).max() / 10.0
    weight = 1.0 - np.exp(-(np.abs(tf_cc)**2) / (m**2))
    nu_t = nu.transpose()

    # highpass filter (periods longer than max_period are suppressed
    # exponentially)
    weight *= (1.0 - np.exp(-(nu_t * max_period)**2))

    # lowpass filter (periods shorter than min_period are suppressed
    # exponentially)
    nu_t_large = np.zeros(nu_t.shape)
    nu_t_small = np.zeros(nu_t.shape)
    thres = (nu_t <= 1.0 / min_period)
    nu_t_large[np.invert(thres)] = 1.0
    nu_t_small[thres] = 1.0
    weight *= (np.exp(-10.0 * np.abs(nu_t * min_period - 1.0)) * nu_t_large +
               nu_t_small)

    # normalisation
    weight /= weight.max()

    # computation of phase difference, make quality checks and misfit ---------

    # Compute the phase difference.
    # DP = np.imag(np.log(m + tf_cc / (2 * m + np.abs(tf_cc))))
    DP = np.angle(tf_cc)

    # Attempt to detect phase jumps by taking the derivatives in time and
    # frequency direction. 0.7 is an emperical value.
    test_field = weight * DP / np.abs(weight * DP).max()
    criterion_1 = np.sum([np.abs(np.diff(test_field, axis=0)) > 0.7])
    criterion_2 = np.sum([np.abs(np.diff(test_field, axis=1)) > 0.7])
    criterion = np.sum([criterion_1, criterion_2])
    # criterion_1 = np.abs(np.diff(test_field, axis=0)).max()
    # criterion_2 = np.abs(np.diff(test_field, axis=1)).max()
    # criterion = max(criterion_1, criterion_2)
    if criterion > 7.0:
        warning = ("Possible phase jump detected. Misfit included. No "
                   "adjoint source computed.")
        warnings.warn(warning)
        messages.append(warning)

    # Compute the phase misfit
    dnu = nu[1, 0] - nu[0, 0]
    phase_misfit = np.sqrt(np.sum(weight**2 * DP**2) * dt_new * dnu)

    # Sanity check. Should not occur.
    if np.isnan(phase_misfit):
        msg = "The phase misfit is NaN."
        raise LASIFAdjointSourceCalculationError(msg)

    # compute the adjoint source when no phase jump detected ------------------

    if criterion <= 7.0:
        # Make kernel for the inverse tf transform
        idp = weight * weight * DP * tf_synth / (
            m + np.abs(tf_synth) * np.abs(tf_synth))

        # Invert tf transform and make adjoint source
        ad_src, it, I = time_frequency.itfa(tau, nu, idp, width)

        # Interpolate to original time axis
        current_time = tau[0, :]
        new_time = t[t <= current_time.max()]
        ad_src = interp1d(current_time, np.imag(ad_src), kind=2)(new_time)
        if len(t) > len(new_time):
            ad_src = np.concatenate([ad_src, np.zeros(len(t) - len(new_time))])

        # Divide by the misfit and change sign.
        ad_src /= (phase_misfit + eps)
        ad_src = -1.0 * np.diff(ad_src) / (t[1] - t[0])

        # Reverse time and add a leading zero so the adjoint source has the
        # same length as the input time series.
        ad_src = ad_src[::-1]
        ad_src = np.concatenate([[0.0], ad_src])

    else:
        # Criterion failed, no misfit and adjoint source calculated.
        raise LASIFAdjointSourceCalculationError(
            "Criterion failed, no misfit has been calculated.")

    # Plot if required. -------------------------------------------------------

    if axis:
        import matplotlib.cm as cm
        import matplotlib.pyplot as plt

        # Primary axis: plot weighted phase difference. -----------------------

        weighted_phase_difference = (DP * weight).transpose()
        abs_diff = np.abs(weighted_phase_difference)
        mappable = axis.pcolormesh(tau,
                                   nu,
                                   weighted_phase_difference,
                                   vmin=-1.0,
                                   vmax=1.0,
                                   cmap=cm.RdBu_r)
        axis.set_xlabel("Seconds since event")
        axis.set_ylabel("Frequency [Hz]")

        # Smart scaling for the frequency axis.
        temp = abs_diff.max(axis=1) * (nu[1] - nu[0])
        ymax = len(temp[temp > temp.max() / 1000.0])
        ymax *= nu[1, 0] - nu[0, 0]
        ymax *= 2
        axis.set_ylim(0, 2.0 / min_period)

        if colorbar_axis:
            cm = plt.gcf().colorbar(mappable, cax=colorbar_axis)
        else:
            cm = plt.gcf().colorbar(mappable, ax=axis)
        cm.set_label("Phase difference in radian")

        # Secondary axis: plot waveforms and adjoint source. ------------------

        ax2 = axis.twinx()

        ax2.plot(t, ad_src, color="black", alpha=1.0)
        min_value = min(ad_src.min(), -1.0)
        max_value = max(ad_src.max(), 1.0)

        value_range = max_value - min_value
        axis.twin_axis = ax2
        ax2.set_ylim(min_value - 2.5 * value_range,
                     max_value + 0.5 * value_range)
        axis.set_xlim(0, tau[:, -1][-1])
        ax2.set_xlim(0, tau[:, -1][-1])
        ax2.set_yticks([])

        text = "Misfit: %.4f" % phase_misfit
        axis.text(x=0.99,
                  y=0.02,
                  s=text,
                  transform=axis.transAxes,
                  bbox=dict(facecolor='orange', alpha=0.8),
                  verticalalignment="bottom",
                  horizontalalignment="right")

        if messages:
            message = "\n".join(messages)
            axis.text(x=0.99,
                      y=0.98,
                      s=message,
                      transform=axis.transAxes,
                      bbox=dict(facecolor='red', alpha=0.8),
                      verticalalignment="top",
                      horizontalalignment="right")

    ret_dict = {
        "adjoint_source": ad_src,
        "misfit_value": phase_misfit,
        "details": {
            "messages": messages
        }
    }

    return ret_dict
Example #3
0
def adsrc_tf_phase_misfit(t,
                          data,
                          synthetic,
                          min_period,
                          max_period,
                          plot=False,
                          max_criterion=7.0):
    """
    :rtype: dictionary
    :returns: Return a dictionary with three keys:
        * adjoint_source: The calculated adjoint source as a numpy array
        * misfit: The misfit value
        * messages: A list of strings giving additional hints to what happened
            in the calculation.
    """
    # Assumes that t starts at 0. Pad your data if that is not the case -
    # Parts with zeros are essentially skipped making it fairly efficient.
    assert t[0] == 0

    messages = []

    # Internal sampling interval. Some explanations for this "magic" number.
    # LASIF's preprocessing allows no frequency content with smaller periods
    # than min_period / 2.2 (see function_templates/preprocesssing_function.py
    # for details). Assuming most users don't change this, this is equal to
    # the Nyquist frequency and the largest possible sampling interval to
    # catch everything is min_period / 4.4.
    #
    # The current choice is historic as changing does (very slightly) chance
    # the calculated misfit and we don't want to disturb inversions in
    # progress. The difference is likely minimal in any case. We might have
    # same aliasing into the lower frequencies but the filters coupled with
    # the TF-domain weighting will get rid of them in essentially all
    # realistically occurring cases.
    dt_new = max(float(int(min_period / 3.0)), t[1] - t[0])

    # New time axis
    ti = utils.matlab_range(t[0], t[-1], dt_new)
    # Make sure its odd - that avoid having to deal with some issues
    # regarding frequency bin interpolation. Now positive and negative
    # frequencies will always be all symmetric. Data is assumed to be
    # tapered in any case so no problem are to be expected.
    if not len(ti) % 2:
        ti = ti[:-1]

    # Interpolate both signals to the new time axis - this massively speeds
    # up the whole procedure as most signals are highly oversampled. The
    # adjoint source at the end is re-interpolated to the original sampling
    # points.
    original_data = data
    original_synthetic = synthetic
    data = lanczos_interpolation(data=data,
                                 old_start=t[0],
                                 old_dt=t[1] - t[0],
                                 new_start=t[0],
                                 new_dt=dt_new,
                                 new_npts=len(ti),
                                 a=8,
                                 window="blackmann")
    synthetic = lanczos_interpolation(data=synthetic,
                                      old_start=t[0],
                                      old_dt=t[1] - t[0],
                                      new_start=t[0],
                                      new_dt=dt_new,
                                      new_npts=len(ti),
                                      a=8,
                                      window="blackmann")
    original_time = t
    t = ti

    # -------------------------------------------------------------------------
    # Compute time-frequency representations

    # Window width is twice the minimal period.
    width = 2.0 * min_period

    # Compute time-frequency representation of the cross-correlation
    _, _, tf_cc = time_frequency.time_frequency_cc_difference(
        t, data, synthetic, width)
    # Compute the time-frequency representation of the synthetic
    tau, nu, tf_synth = time_frequency.time_frequency_transform(
        t, synthetic, width)

    # -------------------------------------------------------------------------
    # compute tf window and weighting function

    # noise taper: down-weight tf amplitudes that are very low
    tf_cc_abs = np.abs(tf_cc)
    m = tf_cc_abs.max() / 10.0  # NOQA
    weight = ne.evaluate("1.0 - exp(-(tf_cc_abs ** 2) / (m ** 2))")

    nu_t = nu.T

    # highpass filter (periods longer than max_period are suppressed
    # exponentially)
    weight *= (1.0 - np.exp(-(nu_t * max_period)**2))

    # lowpass filter (periods shorter than min_period are suppressed
    # exponentially)
    nu_t_large = np.zeros(nu_t.shape)
    nu_t_small = np.zeros(nu_t.shape)
    thres = (nu_t <= 1.0 / min_period)
    nu_t_large[np.invert(thres)] = 1.0
    nu_t_small[thres] = 1.0
    weight *= (np.exp(-10.0 * np.abs(nu_t * min_period - 1.0)) * nu_t_large +
               nu_t_small)

    # normalisation
    weight /= weight.max()

    # computation of phase difference, make quality checks and misfit ---------

    # Compute the phase difference.
    # DP = np.imag(np.log(m + tf_cc / (2 * m + np.abs(tf_cc))))
    DP = np.angle(tf_cc)

    # Attempt to detect phase jumps by taking the derivatives in time and
    # frequency direction. 0.7 is an emperical value.
    abs_weighted_DP = np.abs(weight * DP)
    _x = abs_weighted_DP.max()  # NOQA
    test_field = ne.evaluate("weight * DP / _x")

    criterion_1 = np.sum([np.abs(np.diff(test_field, axis=0)) > 0.7])
    criterion_2 = np.sum([np.abs(np.diff(test_field, axis=1)) > 0.7])
    criterion = np.sum([criterion_1, criterion_2])
    # Compute the phase misfit
    dnu = nu[1] - nu[0]

    i = ne.evaluate("sum(weight ** 2 * DP ** 2)")

    phase_misfit = np.sqrt(i * dt_new * dnu)

    # Sanity check. Should not occur.
    if np.isnan(phase_misfit):
        msg = "The phase misfit is NaN."
        raise LASIFAdjointSourceCalculationError(msg)

    # The misfit can still be computed, even if not adjoint source is
    # available.
    if criterion > max_criterion:
        warning = ("Possible phase jump detected. Misfit included. No "
                   "adjoint source computed. Criterion: %.1f - Max allowed "
                   "criterion: %.1f" % (criterion, max_criterion))
        warnings.warn(warning)
        messages.append(warning)

        ret_dict = {
            "adjoint_source": None,
            "misfit_value": phase_misfit,
            "details": {
                "messages": messages
            }
        }

        return ret_dict

    # Make kernel for the inverse tf transform
    idp = ne.evaluate("weight ** 2 * DP * tf_synth / (m + abs(tf_synth) ** 2)")

    # Invert tf transform and make adjoint source
    ad_src, it, I = time_frequency.itfa(tau, idp, width)

    # Interpolate both signals to the new time axis
    ad_src = lanczos_interpolation(
        # Pad with a couple of zeros in case some where lost in all
        # these resampling operations. The first sample should not
        # change the time.
        data=np.concatenate([ad_src.imag, np.zeros(100)]),
        old_start=tau[0],
        old_dt=tau[1] - tau[0],
        new_start=original_time[0],
        new_dt=original_time[1] - original_time[0],
        new_npts=len(original_time),
        a=8,
        window="blackmann")

    # Divide by the misfit and change sign.
    ad_src /= (phase_misfit + eps)
    ad_src = -1.0 * np.diff(ad_src) / (t[1] - t[0])

    # Taper at both ends. Exploit ObsPy to not have to deal with all the
    # nasty things.
    ad_src = \
        obspy.Trace(ad_src).taper(max_percentage=0.05, type="hann").data

    # Reverse time and add a leading zero so the adjoint source has the
    # same length as the input time series.
    ad_src = ad_src[::-1]
    ad_src = np.concatenate([[0.0], ad_src])

    # Plot if requested. ------------------------------------------------------
    if plot:
        import matplotlib as mpl
        import matplotlib.pyplot as plt
        plt.style.use("seaborn-whitegrid")
        from lasif.colors import get_colormap

        if isinstance(plot, mpl.figure.Figure):
            fig = plot
        else:
            fig = plt.gcf()

        # Manually set-up the axes for full control.
        l, b, w, h = 0.1, 0.05, 0.80, 0.22
        rect = l, b + 3 * h, w, h
        waveforms_axis = fig.add_axes(rect)
        rect = l, b + h, w, 2 * h
        tf_axis = fig.add_axes(rect)
        rect = l, b, w, h
        adj_src_axis = fig.add_axes(rect)
        rect = l + w + 0.02, b, 1.0 - (l + w + 0.02) - 0.05, 4 * h
        cm_axis = fig.add_axes(rect)

        # Plot the weighted phase difference.
        weighted_phase_difference = (DP * weight).transpose()
        mappable = tf_axis.pcolormesh(
            tau,
            nu,
            weighted_phase_difference,
            vmin=-1.0,
            vmax=1.0,
            cmap=get_colormap("tomo_full_scale_linear_lightness_r"),
            shading="gouraud",
            zorder=-10)
        tf_axis.grid(True)
        tf_axis.grid(True,
                     which='minor',
                     axis='both',
                     linestyle='-',
                     color='k')

        cm = fig.colorbar(mappable, cax=cm_axis)
        cm.set_label("Phase difference in radian", fontsize="large")

        # Various texts on the time frequency domain plot.
        text = "Misfit: %.4f" % phase_misfit
        tf_axis.text(x=0.99,
                     y=0.02,
                     s=text,
                     transform=tf_axis.transAxes,
                     fontsize="large",
                     color="#C25734",
                     fontweight=900,
                     verticalalignment="bottom",
                     horizontalalignment="right")

        txt = "Weighted Phase Difference - red is a phase advance of the " \
              "synthetics"
        tf_axis.text(x=0.99,
                     y=0.95,
                     s=txt,
                     fontsize="large",
                     color="0.1",
                     transform=tf_axis.transAxes,
                     verticalalignment="top",
                     horizontalalignment="right")

        if messages:
            message = "\n".join(messages)
            tf_axis.text(x=0.99,
                         y=0.98,
                         s=message,
                         transform=tf_axis.transAxes,
                         bbox=dict(facecolor='red', alpha=0.8),
                         verticalalignment="top",
                         horizontalalignment="right")

        # Adjoint source.
        adj_src_axis.plot(original_time,
                          ad_src[::-1],
                          color="0.1",
                          lw=2,
                          label="Adjoint source (non-time-reversed)")
        adj_src_axis.legend()

        # Waveforms.
        waveforms_axis.plot(original_time,
                            original_data,
                            color="0.1",
                            lw=2,
                            label="Observed")
        waveforms_axis.plot(original_time,
                            original_synthetic,
                            color="#C11E11",
                            lw=2,
                            label="Synthetic")
        waveforms_axis.legend()

        # Set limits for all axes.
        tf_axis.set_ylim(0, 2.0 / min_period)
        tf_axis.set_xlim(0, tau[-1])
        adj_src_axis.set_xlim(0, tau[-1])
        waveforms_axis.set_xlim(0, tau[-1])

        waveforms_axis.set_ylabel("Velocity [m/s]", fontsize="large")
        tf_axis.set_ylabel("Period [s]", fontsize="large")
        adj_src_axis.set_xlabel("Seconds since event", fontsize="large")

        # Hack to keep ticklines but remove the ticks - there is probably a
        # better way to do this.
        waveforms_axis.set_xticklabels(
            ["" for _i in waveforms_axis.get_xticks()])
        tf_axis.set_xticklabels(["" for _i in tf_axis.get_xticks()])

        _l = tf_axis.get_ylim()
        _r = _l[1] - _l[0]
        _t = tf_axis.get_yticks()
        _t = _t[(_l[0] + 0.1 * _r < _t) & (_t < _l[1] - 0.1 * _r)]

        tf_axis.set_yticks(_t)
        tf_axis.set_yticklabels(["%.1fs" % (1.0 / _i) for _i in _t])

        waveforms_axis.get_yaxis().set_label_coords(-0.08, 0.5)
        tf_axis.get_yaxis().set_label_coords(-0.08, 0.5)

        fig.suptitle("Time Frequency Phase Misfit and Adjoint Source",
                     fontsize="xx-large")

    ret_dict = {
        "adjoint_source": ad_src,
        "misfit_value": phase_misfit,
        "details": {
            "messages": messages
        }
    }

    return ret_dict
Example #4
0
def adsrc_tf_phase_misfit(t, data, synthetic, min_period, max_period,
                          plot=False):
    """
    :rtype: dictionary
    :returns: Return a dictionary with three keys:
        * adjoint_source: The calculated adjoint source as a numpy array
        * misfit: The misfit value
        * messages: A list of strings giving additional hints to what happened
            in the calculation.
    """
    # Assumes that t starts at 0. Pad your data if that is not the case -
    # Parts with zeros are essentially skipped making it fairly efficient.
    assert t[0] == 0

    messages = []

    # Internal sampling interval. Some explanations for this "magic" number.
    # LASIF's preprocessing allows no frequency content with smaller periods
    # than min_period / 2.2 (see function_templates/preprocesssing_function.py
    # for details). Assuming most users don't change this, this is equal to
    # the Nyquist frequency and the largest possible sampling interval to
    # catch everything is min_period / 4.4.
    #
    # The current choice is historic as changing does (very slightly) chance
    # the calculated misfit and we don't want to disturb inversions in
    # progress. The difference is likely minimal in any case. We might have
    # same aliasing into the lower frequencies but the filters coupled with
    # the TF-domain weighting will get rid of them in essentially all
    # realistically occurring cases.
    dt_new = max(float(int(min_period / 3.0)), t[1] - t[0])

    # New time axis
    ti = utils.matlab_range(t[0], t[-1], dt_new)
    # Make sure its odd - that avoid having to deal with some issues
    # regarding frequency bin interpolation. Now positive and negative
    # frequencies will always be all symmetric. Data is assumed to be
    # tapered in any case so no problem are to be expected.
    if not len(ti) % 2:
        ti = ti[:-1]

    # Interpolate both signals to the new time axis - this massively speeds
    # up the whole procedure as most signals are highly oversampled. The
    # adjoint source at the end is re-interpolated to the original sampling
    # points.
    original_data = data
    original_synthetic = synthetic
    data = lanczos_interpolation(
        data=data, old_start=t[0], old_dt=t[1] - t[0], new_start=t[0],
        new_dt=dt_new, new_npts=len(ti), a=8, window="blackmann")
    synthetic = lanczos_interpolation(
        data=synthetic, old_start=t[0], old_dt=t[1] - t[0], new_start=t[0],
        new_dt=dt_new, new_npts=len(ti), a=8, window="blackmann")
    original_time = t
    t = ti

    # -------------------------------------------------------------------------
    # Compute time-frequency representations

    # Window width is twice the minimal period.
    width = 2.0 * min_period

    # Compute time-frequency representation of the cross-correlation
    _, _, tf_cc = time_frequency.time_frequency_cc_difference(
        t, data, synthetic, width)
    # Compute the time-frequency representation of the synthetic
    tau, nu, tf_synth = time_frequency.time_frequency_transform(t, synthetic,
                                                                width)

    # -------------------------------------------------------------------------
    # compute tf window and weighting function

    # noise taper: down-weight tf amplitudes that are very low
    tf_cc_abs = np.abs(tf_cc)
    m = tf_cc_abs.max() / 10.0  # NOQA
    weight = ne.evaluate("1.0 - exp(-(tf_cc_abs ** 2) / (m ** 2))")

    nu_t = nu.T

    # highpass filter (periods longer than max_period are suppressed
    # exponentially)
    weight *= (1.0 - np.exp(-(nu_t * max_period) ** 2))

    # lowpass filter (periods shorter than min_period are suppressed
    # exponentially)
    nu_t_large = np.zeros(nu_t.shape)
    nu_t_small = np.zeros(nu_t.shape)
    thres = (nu_t <= 1.0 / min_period)
    nu_t_large[np.invert(thres)] = 1.0
    nu_t_small[thres] = 1.0
    weight *= (np.exp(-10.0 * np.abs(nu_t * min_period - 1.0)) * nu_t_large +
               nu_t_small)

    # normalisation
    weight /= weight.max()

    # computation of phase difference, make quality checks and misfit ---------

    # Compute the phase difference.
    # DP = np.imag(np.log(m + tf_cc / (2 * m + np.abs(tf_cc))))
    DP = np.angle(tf_cc)

    # Attempt to detect phase jumps by taking the derivatives in time and
    # frequency direction. 0.7 is an emperical value.
    abs_weighted_DP = np.abs(weight * DP)
    _x = abs_weighted_DP.max()  # NOQA
    test_field = ne.evaluate("weight * DP / _x")

    criterion_1 = np.sum([np.abs(np.diff(test_field, axis=0)) > 0.7])
    criterion_2 = np.sum([np.abs(np.diff(test_field, axis=1)) > 0.7])
    criterion = np.sum([criterion_1, criterion_2])
    if criterion > 7.0:
        warning = ("Possible phase jump detected. Misfit included. No "
                   "adjoint source computed.")
        warnings.warn(warning)
        messages.append(warning)

    # Compute the phase misfit
    dnu = nu[1] - nu[0]

    i = ne.evaluate("sum(weight ** 2 * DP ** 2)")

    phase_misfit = np.sqrt(i * dt_new * dnu)

    # Sanity check. Should not occur.
    if np.isnan(phase_misfit):
        msg = "The phase misfit is NaN."
        raise LASIFAdjointSourceCalculationError(msg)

    # compute the adjoint source when no phase jump detected ------------------

    if criterion <= 7.0:
        # Make kernel for the inverse tf transform
        idp = ne.evaluate(
            "weight ** 2 * DP * tf_synth / (m + abs(tf_synth) ** 2)")

        # Invert tf transform and make adjoint source
        ad_src, it, I = time_frequency.itfa(tau, idp, width)

        # Interpolate both signals to the new time axis
        ad_src = lanczos_interpolation(
            # Pad with a couple of zeros in case some where lost in all
            # these resampling operations. The first sample should not
            # change the time.
            data=np.concatenate([ad_src.imag, np.zeros(100)]),
            old_start=tau[0],
            old_dt=tau[1] - tau[0],
            new_start=original_time[0],
            new_dt=original_time[1] - original_time[0],
            new_npts=len(original_time), a=8, window="blackmann")

        # Divide by the misfit and change sign.
        ad_src /= (phase_misfit + eps)
        ad_src = -1.0 * np.diff(ad_src) / (t[1] - t[0])

        # Taper at both ends. Exploit ObsPy to not have to deal with all the
        # nasty things.
        ad_src = \
            obspy.Trace(ad_src).taper(max_percentage=0.05, type="hann").data

        # Reverse time and add a leading zero so the adjoint source has the
        # same length as the input time series.
        ad_src = ad_src[::-1]
        ad_src = np.concatenate([[0.0], ad_src])

    else:
        # Criterion failed, no misfit and adjoint source calculated.
        raise LASIFAdjointSourceCalculationError(
            "Criterion failed, no misfit has been calculated.")

    # Plot if requested. ------------------------------------------------------
    if plot:
        import matplotlib as mpl
        import matplotlib.pyplot as plt
        plt.style.use("seaborn-whitegrid")
        from lasif.colors import get_colormap

        if isinstance(plot, mpl.figure.Figure):
            fig = plot
        else:
            fig = plt.gcf()

        # Manually set-up the axes for full control.
        l, b, w, h = 0.1, 0.05, 0.80, 0.22
        rect = l, b + 3 * h, w, h
        waveforms_axis = fig.add_axes(rect)
        rect = l, b + h, w, 2 * h
        tf_axis = fig.add_axes(rect)
        rect = l, b, w, h
        adj_src_axis = fig.add_axes(rect)
        rect = l + w + 0.02, b, 1.0 - (l + w + 0.02) - 0.05, 4 * h
        cm_axis = fig.add_axes(rect)

        # Plot the weighted phase difference.
        weighted_phase_difference = (DP * weight).transpose()
        mappable = tf_axis.pcolormesh(
            tau, nu, weighted_phase_difference, vmin=-1.0, vmax=1.0,
            cmap=get_colormap("tomo_full_scale_linear_lightness_r"),
            shading="gouraud", zorder=-10)
        tf_axis.grid(True)
        tf_axis.grid(True, which='minor', axis='both', linestyle='-',
                     color='k')

        cm = fig.colorbar(mappable, cax=cm_axis)
        cm.set_label("Phase difference in radian", fontsize="large")

        # Various texts on the time frequency domain plot.
        text = "Misfit: %.4f" % phase_misfit
        tf_axis.text(x=0.99, y=0.02, s=text, transform=tf_axis.transAxes,
                     fontsize="large", color="#C25734", fontweight=900,
                     verticalalignment="bottom",
                     horizontalalignment="right")

        txt = "Weighted Phase Difference - red is a phase advance of the " \
              "synthetics"
        tf_axis.text(x=0.99, y=0.95, s=txt,
                     fontsize="large", color="0.1",
                     transform=tf_axis.transAxes,
                     verticalalignment="top",
                     horizontalalignment="right")

        if messages:
            message = "\n".join(messages)
            tf_axis.text(x=0.99, y=0.98, s=message,
                         transform=tf_axis.transAxes,
                         bbox=dict(facecolor='red', alpha=0.8),
                         verticalalignment="top",
                         horizontalalignment="right")

        # Adjoint source.
        adj_src_axis.plot(original_time, ad_src[::-1], color="0.1", lw=2,
                          label="Adjoint source (non-time-reversed)")
        adj_src_axis.legend()

        # Waveforms.
        waveforms_axis.plot(original_time, original_data, color="0.1", lw=2,
                            label="Observed")
        waveforms_axis.plot(original_time, original_synthetic,
                            color="#C11E11", lw=2, label="Synthetic")
        waveforms_axis.legend()

        # Set limits for all axes.
        tf_axis.set_ylim(0, 2.0 / min_period)
        tf_axis.set_xlim(0, tau[-1])
        adj_src_axis.set_xlim(0, tau[-1])
        waveforms_axis.set_xlim(0, tau[-1])

        waveforms_axis.set_ylabel("Velocity [m/s]", fontsize="large")
        tf_axis.set_ylabel("Period [s]", fontsize="large")
        adj_src_axis.set_xlabel("Seconds since event", fontsize="large")

        # Hack to keep ticklines but remove the ticks - there is probably a
        # better way to do this.
        waveforms_axis.set_xticklabels([
            "" for _i in waveforms_axis.get_xticks()])
        tf_axis.set_xticklabels(["" for _i in tf_axis.get_xticks()])

        _l = tf_axis.get_ylim()
        _r = _l[1] - _l[0]
        _t = tf_axis.get_yticks()
        _t = _t[(_l[0] + 0.1 * _r < _t) & (_t < _l[1] - 0.1 * _r)]

        tf_axis.set_yticks(_t)
        tf_axis.set_yticklabels(["%.1fs" % (1.0 / _i) for _i in _t])

        waveforms_axis.get_yaxis().set_label_coords(-0.08, 0.5)
        tf_axis.get_yaxis().set_label_coords(-0.08, 0.5)

        fig.suptitle("Time Frequency Phase Misfit and Adjoint Source",
                     fontsize="xx-large")

    ret_dict = {
        "adjoint_source": ad_src,
        "misfit_value": phase_misfit,
        "details": {"messages": messages}
    }

    return ret_dict
Example #5
0
def adsrc_cc_time_shift(t, data, synthetic, min_period, max_period,
                        axis=None, colorbar_axis=None):
    """
    :rtype: dictionary
    :returns: Return a dictionary with three keys:
        * adjoint_source: The calculated adjoint source as a numpy array
        * misfit: The misfit value
        * messages: A list of strings giving additional hints to what happened
            in the calculation.
    """

    messages = []

    # Compute time-frequency representations ----------------------------------

    # compute new time increments and Gaussian window width for the
    # time-frequency transforms
    dt_new = float(int(min_period / 3.0))
    width = 2.0 * min_period

    # Compute time-frequency representation of the cross-correlation
    tau_cc, nu_cc, tf_cc = time_frequency.time_frequency_cc_difference(
        t, data, synthetic, dt_new, width)
    # Compute the time-frequency representation of the synthetic
    tau, nu, tf_synth = time_frequency.time_frequency_transform(t, synthetic,
                                                                dt_new, width)

    # 2D interpolation to bring the tf representation of the correlation on the
    # same grid as the tf representation of the synthetics. Uses a two-step
    # procedure for real and imaginary parts.
    tf_cc_interp = RectBivariateSpline(tau_cc[0], nu_cc[:, 0], tf_cc.real,
                                       kx=1, ky=1, s=0)(tau[0], nu[:, 0])
    tf_cc_interp = np.require(tf_cc_interp, dtype="complex128")
    tf_cc_interp.imag = RectBivariateSpline(tau_cc[0], nu_cc[:, 0], tf_cc.imag,
                                            kx=1, ky=1, s=0)(tau[0], nu[:, 0])
    tf_cc = tf_cc_interp

    # compute tf window and weighting function --------------------------------

    # noise taper: downweigh tf amplitudes that are very low
    m = np.abs(tf_cc).max() / 10.0
    weight = 1.0 - np.exp(-(np.abs(tf_cc) ** 2) / (m ** 2))
    nu_t = nu.transpose()

    # highpass filter (periods longer than max_period are suppressed
    # exponentially)
    weight *= (1.0 - np.exp(-(nu_t * max_period) ** 2))

    # lowpass filter (periods shorter than min_period are suppressed
    # exponentially)
    nu_t_large = np.zeros(nu_t.shape)
    nu_t_small = np.zeros(nu_t.shape)
    thres = (nu_t <= 1.0 / min_period)
    nu_t_large[np.invert(thres)] = 1.0
    nu_t_small[thres] = 1.0
    weight *= (np.exp(-10.0 * np.abs(nu_t * min_period - 1.0)) * nu_t_large +
               nu_t_small)

    # normalisation
    weight /= weight.max()

    # computation of phase difference, make quality checks and misfit ---------

    # Compute the phase difference.
    # DP = np.imag(np.log(m + tf_cc / (2 * m + np.abs(tf_cc))))
    DP = np.angle(tf_cc)

    # Attempt to detect phase jumps by taking the derivatives in time and
    # frequency direction. 0.7 is an emperical value.
    test_field = weight * DP / np.abs(weight * DP).max()
    criterion_1 = np.sum([np.abs(np.diff(test_field, axis=0)) > 0.7])
    criterion_2 = np.sum([np.abs(np.diff(test_field, axis=1)) > 0.7])
    criterion = np.sum([criterion_1, criterion_2])
    # criterion_1 = np.abs(np.diff(test_field, axis=0)).max()
    # criterion_2 = np.abs(np.diff(test_field, axis=1)).max()
    # criterion = max(criterion_1, criterion_2)
    if criterion > 7.0:
        warning = ("Possible phase jump detected. Misfit included. No "
                   "adjoint source computed.")
        warnings.warn(warning)
        messages.append(warning)

    # Compute the phase misfit
    dnu = nu[1, 0] - nu[0, 0]
    phase_misfit = np.sqrt(np.sum(weight ** 2 * DP ** 2) * dt_new * dnu)

    # Sanity check. Should not occur.
    if np.isnan(phase_misfit):
        msg = "The phase misfit is NaN."
        raise Exception(msg)

    # compute the adjoint source when no phase jump detected ------------------

    if criterion <= 7.0:
        # Make kernel for the inverse tf transform
        idp = weight * weight * DP * tf_synth / (m + np.abs(tf_synth) *
                                                 np.abs(tf_synth))

        # Invert tf transform and make adjoint source
        ad_src, it, I = time_frequency.itfa(tau, nu, idp, width)

        # Interpolate to original time axis
        current_time = tau[0, :]
        new_time = t[t <= current_time.max()]
        ad_src = interp1d(current_time, np.imag(ad_src), kind=2)(new_time)
        if len(t) > len(new_time):
            ad_src = np.concatenate([ad_src, np.zeros(len(t) - len(new_time))])

        # Divide by the misfit.
        ad_src /= (phase_misfit + eps)
        ad_src = np.diff(ad_src) / (t[1] - t[0])

        # Reverse time and add a leading zero so the adjoint source has the
        # same length as the input time series.
        ad_src = ad_src[::-1]
        ad_src = np.concatenate([[0.0], ad_src])

    else:
        ad_src = np.zeros(len(t))

    # Plot if required. -------------------------------------------------------

    if axis:
        import matplotlib.cm as cm
        import matplotlib.pyplot as plt

        # Primary axis: plot weighted phase difference. -----------------------

        weighted_phase_difference = (DP * weight).transpose()
        abs_diff = np.abs(weighted_phase_difference)
        mappable = axis.pcolormesh(tau, nu, weighted_phase_difference,
                                   vmin=-1.0, vmax=1.0, cmap=cm.RdBu_r)
        axis.set_xlabel("Seconds since event")
        axis.set_ylabel("Frequency [Hz]")

        # Smart scaling for the frequency axis.
        temp = abs_diff.max(axis=1) * (nu[1] - nu[0])
        ymax = len(temp[temp > temp.max() / 1000.0])
        ymax *= nu[1, 0] - nu[0, 0]
        ymax *= 2
        axis.set_ylim(0, 2.0 / min_period)

        if colorbar_axis:
            cm = plt.gcf().colorbar(mappable, cax=colorbar_axis)
        else:
            cm = plt.gcf().colorbar(mappable, ax=axis)
        cm.set_label("Phase difference in radian")

        # Secondary axis: plot waveforms and adjoint source. ------------------

        ax2 = axis.twinx()

        ax2.plot(t, ad_src, color="black", alpha=1.0)
        min_value = min(ad_src.min(), -1.0)
        max_value = max(ad_src.max(), 1.0)

        value_range = max_value - min_value
        axis.twin_axis = ax2
        ax2.set_ylim(min_value - 2.5 * value_range,
                     max_value + 0.5 * value_range)
        axis.set_xlim(0, tau[:, -1][-1])
        ax2.set_xlim(0, tau[:, -1][-1])
        ax2.set_yticks([])

        text = "Misfit: %.4f" % phase_misfit
        axis.text(x=0.99, y=0.02, s=text, transform=axis.transAxes,
                  bbox=dict(facecolor='orange', alpha=0.8),
                  verticalalignment="bottom",
                  horizontalalignment="right")

        if messages:
            message = "\n".join(messages)
            axis.text(x=0.99, y=0.98, s=message, transform=axis.transAxes,
                      bbox=dict(facecolor='red', alpha=0.8),
                      verticalalignment="top",
                      horizontalalignment="right")

    ret_dict = {
        "adjoint_source": ad_src,
        "misfit_value": phase_misfit,
        "details": {"messages": messages}
    }

    return ret_dict