def rotate_to_ENZ(st, inventory):
    st_new = st.copy()

    for sta in st_new.unique_stations():

        trs = st_new.select(station=sta)

        if not inventory.select(sta):
            logger.warning(f'missing station "{sta}" in inventory')
            continue

        # catching edge case when a uniaxial sensors contains three traces
        # with two traces containing only NaN.

        if len(trs) == 3:
            if np.any([np.all(np.isnan(trs[0].data)),
                       np.all(np.isnan(trs[1].data)),
                       np.all(np.isnan(trs[2].data))]):
                continue

        if len(trs) == 3 and len(inventory.select(sta).channels) == 3:

            try:
                col1 = inventory.get_channel(sta=sta, cha='X').cosines
                col2 = inventory.get_channel(sta=sta, cha='Y').cosines
                col3 = inventory.get_channel(sta=sta, cha='Z').cosines
            except AttributeError as err:
                logger.error(err)
                continue

            A = np.column_stack((col1, col2, col3))
            At = A.transpose()

            x = trs[0].data
            y = trs[1].data
            z = trs[2].data
            D = np.row_stack((x, y, z))

            foo = At @ D

            trs[0].data = foo[0, :]
            trs[1].data = foo[1, :]
            trs[2].data = foo[2, :]
            trs[0].stats.channel = 'E'
            trs[1].stats.channel = 'N'
            trs[2].stats.channel = 'Z'

    return st_new
Beispiel #2
0
def set_pick_snrs(st, picks, pre_wl=.03, post_wl=.03):
    """
    This function sets the pick snr on each individual trace
     (vs the snr_picker which calculates snr on the composite trace)

    The resulting snr is stored in the tr.stats[key] dict
    where key = {'P_arrival', 'S_arrival'}

    :param st: traces
    :type st: either obspy.core.Stream or uquake.core.Stream
    :param picks: P & S picks
    :type list: list of either obspy or uquake picks
    :param pre_wl: pre pick window for noise calc
    :type float:
    :param post_wl: post pick window for signal calc
    :type float:
    """

    pick_dict = copy_picks_to_dict(picks)

    for tr in st:
        sta = tr.stats.station

        if sta in pick_dict:
            for phase in pick_dict[sta]:
                pick_time = pick_dict[sta][phase].time

                if phase == 'S':
                    snr = calculate_snr(Stream(traces=[tr]),
                                        pick_time,
                                        pre_wl=pre_wl,
                                        post_wl=post_wl)
                else:
                    snr = calculate_snr(Stream(traces=[tr]),
                                        pick_time,
                                        pre_wl=pre_wl,
                                        post_wl=post_wl)
                key = "%s_arrival" % phase

                if key not in tr.stats:
                    tr.stats[key] = {}
                tr.stats[key]['snr'] = snr
        else:
            logger.warning("set_pick_snrs: sta:%s not in pick_dict" % sta)

    return
Beispiel #3
0
def calc_velocity_flux(st_in,
                       cat,
                       inventory,
                       phase_list=None,
                       use_fixed_window=True,
                       pre_P=.01,
                       P_len=.05,
                       pre_S=.01,
                       S_len=.1,
                       Q=1e12,
                       correct_attenuation=False,
                       triaxial_only=True,
                       debug=False):
    """
    For each arrival (on phase_list) calculate the velocity flux using
        the corresponding traces and save to the arrival.vel_flux to
        be used in the calculation of radiated seismic energy

    :param st_in: velocity traces
    :type st_in: obspy.core.Stream or uquake.core.Stream
    :param cat: obspy.core.event.Catalog
    :type cat: list of obspy.core.event.Events or uquake.core.event.Events
    :param phase_list: ['P'], ['S'], or ['P', 'S'] - list of arrival phases
    to process
    :type phase_list: list
    :param triaxial_only: if True --> only calc flux for 3-comp stations
    :type triaxial_only: boolean
    :param Q: Anelastic Q to use for attenuation correction to flux
    :type Q: float
    :param correct_attenuation: if True, scale spec by e^-pi*f*travel-time/Q
    before summing
    :type correct_attenuation: boolean
    """

    fname = "calc_velocity_flux"

    if phase_list is None:
        phase_list = ['P', 'S']

    # MTH: Try to avoid copying cat - it causes the events to lose their
    # link to preferred_origin!
    # cat = cat_in.copy()
    # Defensive copy - not currently needed since we only trim on copies,
    # still ...
    st = st_in.copy().detrend('demean').detrend('linear')

    for event in cat:
        origin = event.preferred_origin() if event.preferred_origin() else \
            event.origins[0]

        for arr in origin.arrivals:

            phase = arr.phase

            if phase not in phase_list:
                continue

            pick = Pick(arr.get_pick())

            if pick is None:
                logger.error(
                    "%s: arr pha:%s id:%s --> Lost reference to "
                    "pick id:%s --> SKIP" %
                    (fname, arr.phase, arr.resource_id.id, arr.pick_id.id))

                continue
            sta = pick.get_sta()

            trs = st.select(station=sta)

            if trs is None:
                logger.warning("%s: sta:%s has a [%s] arrival but no trace "
                               "in stream --> Skip" % (fname, sta, phase))

                continue

            if triaxial_only and len(trs) != 3:
                logger.info("%s: sta:%s is not 3-comp --> Skip" % (fname, sta))

                continue

            sensor_type = get_sensor_type_from_trace(trs[0])

            if sensor_type != "VEL":
                logger.info("%s: sta:%s sensor_type != VEL --> Skip" %
                            (fname, sta))

                continue

            if use_fixed_window:
                if phase == 'P':
                    pre = pre_P
                    win_secs = P_len
                else:
                    pre = pre_S
                    win_secs = S_len

                starttime = pick.time - pre
                endtime = starttime + win_secs

            not_enough_trace = False

            for tr in trs:
                if starttime < tr.stats.starttime or endtime > \
                        tr.stats.endtime:
                    logger.warning("%s: sta:%s pha:%s tr:%s is too short to "
                                   "trim --> Don't use" %
                                   (fname, sta, phase, tr.get_id()))
                    not_enough_trace = True

                    break

            if not_enough_trace:
                continue

            tr3 = trs.copy()

            tr3.trim(starttime=starttime, endtime=endtime)
            dt = tr3[0].stats.delta

            # flux_t = np.sum( [tr.data**2 for tr in tr3]) * dt

            tsum = 0

            for tr in tr3:
                tsum += np.sum(tr.data**2) * dt

            if not correct_attenuation:
                flux = tsum
                fsum = None

            # The only reason to do this in the freq domain is if we
            #    want to apply attenuation correction
            else:
                travel_time = pick.time - origin.time

                fsum = 0.

                # exp(pi * f * (R/v) * 1/Q) grows so fast with freq that it's out of
                # control above f ~ 1e3 Hz
                # We could design Q = Q(f) - e.g., make Q grow fast with f to
                # counteract this.
                # Alternatively, we limit the freq range of the (
                # attenuation-corrected) energy calc:
                #   To compare the t/f calcs using Parseval's:
                #   1. Set Q to something like 1e12 in the settings
                #   2. Set fmin=0 so that the low freqs are included in the summationi
                sensor_response = inventory.select(arr.get_sta())
                poles = np.abs(sensor_response[0].response.get_paz().poles)
                fmin = np.min(poles) / (2 * np.pi)
                fmax = 500.  # looking at the response in the frequency
                # domain, beyond 500 Hz, the response is dominated by the
                # brown environmental noise.
                # In addition, it's necessary to locate fmin/fmax for each arrival
                # based on the min/max freqs where the velocity spec exceeds the
                # noise spec.
                # Thus, we can't actually include all the radiated energy, just the
                # energy above the noise level
                #   so these estimates will likely be low
                fmin = arr.fmin
                fmax = arr.fmax

                if fmax / fmin < 3:
                    logger.info("%s: sta:%s fmin:%.1f fmax:%.1f too "
                                "narrowband --> Skip" %
                                (fname, sta, fmin, fmax))

                    continue

                for tr in tr3:
                    data = tr.data
                    nfft = 2 * npow2(data.size)
                    df = 1. / (dt * float(nfft))  # df is same as for
                    y, freqs = unpack_rfft(rfft(data, n=nfft), df)
                    y *= dt
                    y[1:-1] *= np.sqrt(2.)

                    tstar = travel_time / Q

                    index = [(freqs >= fmin) & (freqs <= fmax)]
                    freqs = freqs[index]
                    y = y[index]
                    fsum += np.sum(
                        np.abs(y) * np.abs(y) *
                        np.exp(2. * np.pi * freqs * tstar)) * df

                print("arr sta:%s [%s] tsum=%g fsum=%g fmin=%f fmax=%f" %
                      (sta, arr.phase, tsum, fsum, fmin, fmax))
                # exit()

                flux = fsum

            # Note we are saving the "flux" but it has not (yet) been scaled
            # by rho*vel. Instead it is just the sum of the integrals of the
            # component velocities squared for the corresponding arrival and
            # scaling is done in calc_energy(..)

            # arr.vel_flux = flux
            arr.vel_flux = tsum
            arr.vel_flux_Q = fsum

    return cat
Beispiel #4
0
def measure_displacement_pulse(st, cat, phase_list=None, debug=False):
    """
    measure displacement pulse (area + width) for each pick on each arrival
        as needed for moment magnitude calculation

    All measurements are added to the *arrival* extras dict

    :param st: velocity traces
    :type st: obspy.core.Stream or uquake.core.Stream
    :param cat: obspy.core.event.Catalog
    :type list: list of obspy.core.event.Events or uquake.core.event.Events
    """

    fname = 'measure_displacement_pulse'

    if phase_list is None:
        phase_list = ['P']

    traces_info = []
    for event in cat:
        origin = event.preferred_origin() if event.preferred_origin() else \
            event.origins[0]
        arrivals = origin.arrivals

        for arr in arrivals:

            phase = arr.phase

            if phase not in phase_list:
                continue

            pk = Pick(arr.get_pick())

            if pk is None:
                logger.error(
                    "%s: arr pha:%s id:%s --> Lost reference to "
                    "pick id:%s --> SKIP" %
                    (fname, arr.phase, arr.resource_id.id, arr.pick_id.id))

                continue
            sta = pk.get_sta()

            trs = st.select(station=sta)

            if trs is None:
                logger.warning("%s: sta:%s has a [%s] arrival but no trace "
                               "in stream --> Skip" % (fname, sta, arr.phase))

                continue

            sensor_type = get_sensor_type_from_trace(trs[0])

            if sensor_type != "VEL":
                logger.info("%s: sta:%s sensor_type != VEL --> Skip" %
                            (fname, sta))

                continue

            for tr in trs:
                try:
                    tr_dis = tr.copy().detrend("demean").detrend("linear")
                    tr_dis.integrate().detrend("linear")
                except Exception as e:
                    print(e)

                    continue
                tr_dis.stats.channel = "%s.dis" % tr.stats.channel

                dd = {}
                dd['peak_dis'] = None
                dd['max_dis'] = None
                dd['tpeak_dis'] = None
                dd['tmax_dis'] = None
                dd['dis_pulse_width'] = None
                dd['dis_pulse_area'] = None

                tr_dict = arr.traces[tr.get_id()]

                polarity = tr_dict['polarity']
                t1 = tr_dict.get('t1', None)
                t2 = tr_dict.get('t2', None)

                if polarity != 0:

                    if t1 is None or t2 is None:
                        logger.error("%s: t1 or t2 is None --> You shouldn't "
                                     "be here!" % (fname))

                        continue

                    i1 = int(
                        (t1 - tr.stats.starttime) * tr.stats.sampling_rate)
                    i2 = int(
                        (t2 - tr.stats.starttime) * tr.stats.sampling_rate)

                    ipick = int((pk.time - tr.stats.starttime) *
                                tr.stats.sampling_rate)

                    icross = i2
                    tr_dis.data = tr_dis.data - tr_dis.data[i1]
                    # tr_dis.data = tr_dis.data - tr_dis.data[ipick]

                    dis_polarity = np.sign(tr_dis.data[icross])
                    pulse_width, pulse_area = _get_pulse_width_and_area(
                        tr_dis, i1, icross)

                    npulse = int(pulse_width * tr.stats.sampling_rate)

                    max_pulse_duration = .08
                    nmax_len = int(max_pulse_duration * tr.stats.sampling_rate)

                    if pulse_width != 0:

                        ipeak, peak_dis = _get_peak_amp(
                            tr_dis, ipick, ipick + npulse)
                        # max_dis = max within max_pulse_duration of pick time
                        imax, max_dis = _get_peak_amp(tr_dis, ipick,
                                                      ipick + nmax_len)

                        tmax_dis = tr.stats.starttime + float(
                            imax * tr.stats.delta)
                        tpeak_dis = tr.stats.starttime + float(
                            ipeak * tr.stats.delta)
                        tcross_dis = pk.time + pulse_width

                        dd['peak_dis'] = peak_dis
                        dd['max_dis'] = max_dis
                        dd['tpeak_dis'] = tpeak_dis
                        dd['tmax_dis'] = tmax_dis
                        dd['dis_pulse_width'] = pulse_width
                        dd['dis_pulse_area'] = pulse_area

                        if debug:
                            logger.debug("[%s] Dis pol=%d tpick=%s" %
                                         (phase, dis_polarity, pk.time))
                            logger.debug("              tpeak=%s "
                                         "peak_dis=%12.10g" %
                                         (tpeak_dis, peak_dis))
                            logger.debug("             tcross=%s" % tcross_dis)
                            logger.debug("               tmax=%s "
                                         "max_dis=%12.10g" %
                                         (tmax_dis, max_dis))
                            logger.debug("    dis pulse width=%.5f" %
                                         pulse_width)
                            logger.debug("    dis pulse  area=%12.10g" %
                                         pulse_area)

                    else:
                        logger.warning(
                            "%s: Got pulse_width=0 for tr:%s pha:%s" %
                            (fname, tr.get_id(), phase))

                arr.traces[tr.get_id()] = dict(tr_dict, **dd)

                dd['trace_id'] = tr.get_id()
                dd['arrival_id'] = arr.resource_id
                dd['event_id'] = event.resource_id
                dd['origin_id'] = origin.resource_id
                traces_info.append(dd)

            # Process next tr in trs

        # Process next arr in arrivals

    # Process next event in cat

    return cat
Beispiel #5
0
def measure_velocity_pulse(
    st,
    cat,
    phase_list=None,
    pulse_min_width=.005,
    pulse_min_snr_P=7,
    pulse_min_snr_S=5,
    debug=False,
):
    """
    locate velocity pulse (zero crossings) near pick and measure peak amp,
        polarity, etc on it

    All measurements are added to the *arrival* extras dict

    :param st: velocity traces
    :type st: obspy.core.Stream or uquake.core.Stream
    :param cat: obspy.core.event.Catalog
    :type cat: list of obspy.core.event.Events or uquake.core.event.Events
    :param phase_list: ['P'], ['S'], or ['P', 'S'] - list of arrival phases
    to process
    :type phase_list: list
    :param pulse_min_width: Measured first pulse must be this wide to be
    retained
    :type pulse_min_width: float
    :param pulse_min_snr_P: Measure first P pulse must have snr greater
    than this
    :type pulse_min_snr_P: float
    :param pulse_min_snr_S: Measure first S pulse must have snr greater
    than this
    :type pulse_min_snr_S: float
    """

    fname = 'measure_velocity_pulse'

    traces_info = []

    if phase_list is None:
        phase_list = ['P']

    # Average of P,S min snr used for finding zeros
    min_pulse_snr = int((pulse_min_snr_P + pulse_min_snr_S) / 2)

    for event in cat:
        origin = event.preferred_origin() if event.preferred_origin() else \
            event.origins[-1]
        arrivals = origin.arrivals

        for arr in arrivals:

            phase = arr.phase

            if phase not in phase_list:
                continue

            pk = Pick(arr.get_pick())

            if pk is None:
                logger.error(
                    "%s: arr pha:%s id:%s --> Lost reference to "
                    "pick id:%s --> SKIP" %
                    (fname, arr.phase, arr.resource_id.id, arr.pick_id.id))

                continue
            sta = pk.get_sta()

            trs = st.select(station=sta)

            if trs is None:
                logger.warning("%s: sta:%s has a [%s] arrival but no trace "
                               "in stream --> Skip" % (fname, sta, arr.phase))
                continue

            sensor_type = get_sensor_type_from_trace(trs[0])

            if sensor_type != "VEL":
                logger.info("%s: sta:%s sensor_type != VEL --> Skip" %
                            (fname, sta))
                continue

            arr.traces = {}

            for tr in trs:
                try:
                    tr.detrend("demean").detrend("linear")
                except Exception as e:
                    print(e)

                    continue
                data = tr.data.copy()
                ipick = int(
                    (pk.time - tr.stats.starttime) * tr.stats.sampling_rate)

                polarity, vel_zeros = _find_signal_zeros(
                    tr,
                    ipick,
                    nzeros_to_find=3,
                    min_pulse_width=pulse_min_width,
                    min_pulse_snr=min_pulse_snr,
                    debug=debug)

                dd = {}
                dd['polarity'] = 0
                dd['t1'] = None
                dd['t2'] = None
                dd['peak_vel'] = None
                dd['tpeak_vel'] = None
                dd['pulse_snr'] = None

                # A good pick will have the first velocity pulse located
                # between i1 and i2

                if vel_zeros is not None:
                    i1 = vel_zeros[0]
                    i2 = vel_zeros[1]
                    t1 = tr.stats.starttime + float(i1 * tr.stats.delta)
                    t2 = tr.stats.starttime + float(i2 * tr.stats.delta)

                    ipeak, peak_vel = _get_peak_amp(tr, i1, i2)
                    tpeak = tr.stats.starttime + float(ipeak * tr.stats.delta)

                    noise_npts = int(.01 * tr.stats.sampling_rate)
                    noise_end = ipick - int(.005 * tr.stats.sampling_rate)
                    noise = data[noise_end - noise_npts:noise_end]
                    noise1 = np.abs(np.mean(noise))
                    noise2 = np.abs(np.median(noise))
                    noise3 = np.abs(np.std(noise))
                    noise_level = np.max([noise1, noise2, noise3])

                    pulse_snr = np.abs(peak_vel / noise_level)
                    pulse_width = float((i2 - i1) * tr.stats.delta)

                    pulse_thresh = pulse_min_snr_P

                    if phase == 'S':
                        pulse_thresh = pulse_min_snr_S

                    if pulse_snr < pulse_thresh:
                        logger.debug(
                            "%s: tr:%s pha:%s t1:%s t2:%s "
                            "pulse_snr=%.1f < thresh" %
                            (fname, tr.get_id(), phase, t1, t2, pulse_snr))
                        polarity = 0

                    if pulse_width < pulse_min_width:
                        logger.debug("%s: tr:%s pha:%s t1:%s t2:%s "
                                     "pulse_width=%f < %f" %
                                     (fname, tr.get_id(), phase, t1, t2,
                                      pulse_width, pulse_min_width))
                        polarity = 0

                    dd['polarity'] = polarity
                    dd['peak_vel'] = peak_vel
                    dd['tpeak_vel'] = tpeak
                    dd['t1'] = t1
                    dd['t2'] = t2
                    dd['pulse_snr'] = pulse_snr

                else:
                    logger.debug(
                        "%s: Unable to locate zeros for tr:%s pha:%s" %
                        (fname, tr.get_id(), phase))

                arr.traces[tr.get_id()] = dd
                dd['trace_id'] = tr.get_id()
                dd['arrival_id'] = arr.resource_id
                dd['event_id'] = event.resource_id
                dd['origin_id'] = origin.resource_id
                traces_info.append(dd)

            # Process next phase in phase_list

        # Process tr in st

    # Process next event in cat

    return cat
def rotate_to_P_SV_SH(st, cat, debug=False):
    fname = 'rotate_to_P_SV_SH'

    st_new = st.copy()

    event = cat[0]
    for arr in event.preferred_origin().arrivals:
        if arr.phase == 'S':
            continue

        pk = arr.pick_id.get_referred_object()
        sta = pk.waveform_id.station_code
        baz = arr.backazimuth
        az = arr.azimuth
        takeoff = arr.takeoff_angle
        inc_angle = arr.inc_angle

        if inc_angle is None:
            baz, inc_angle = event.preferred_origin(
            ).get_incidence_baz_angles(sta, arr.phase)
            inc_angle *= 180 / np.pi

        if inc_angle is None:
            logger.warning("%s: sta:%s [%s] has inc_angle=None --> skip "
                           "rotation!" % (fname, sta, arr.phase))
            continue

        trs = st_new.select(station=sta)
        if len(trs) == 3:

            cos_i = np.cos(inc_angle * np.pi / 180.)
            sin_i = np.sin(inc_angle * np.pi / 180.)
            cos_baz = np.cos(baz * np.pi / 180.)
            sin_baz = np.sin(baz * np.pi / 180.)

            col1 = np.array([cos_i, sin_i, 0.])
            col2 = np.array([-sin_i * sin_baz, cos_i * sin_baz, -cos_baz])
            col3 = np.array([-sin_i * cos_baz, cos_i * cos_baz, sin_baz])

            A = np.column_stack((col1, col2, col3))

            if debug:
                print("sta:%s az:%.1f baz:%.1f takeoff:%.1f inc:%.1f" % (
                sta, az, baz, takeoff, inc_angle))

            E = trs[0].data
            N = trs[1].data
            Z = trs[2].data
            D = np.row_stack((Z, E, N))

            foo = A @ D

            # if sta in ['59', '87']:
            # trs.plot()

            trs[0].data = foo[0, :]
            trs[1].data = foo[1, :]
            trs[2].data = foo[2, :]
            trs[0].stats.channel = 'P'
            trs[1].stats.channel = 'SV'
            trs[2].stats.channel = 'SH'

            '''
            P = trs[0].copy().trim(starttime = pk.time -.02, endtime=pk.time +.02)
            SV = trs[1].copy().trim(starttime = pk.time -.02, endtime=pk.time +.02)
            SH = trs[2].copy().trim(starttime = pk.time -.02, endtime=pk.time +.02)

            S = np.sqrt(SV.data**2 + SH.data**2)
            print(type(S))
            print(S)

            PtoS = np.var(P.data)/np.var(S)
            print(type(PtoS))
            print(PtoS)

            print("P_max:%g SV_max:%g SH_max:%g P/S:%f" % (np.max(np.abs(P.data)), np.max(np.abs(SV.data)), \
                                                    np.max(np.abs(SH.data), PtoS)))

            #if sta in ['59', '87']:
                #trs.plot()
            #exit()
            '''


        else:
            print("sta:%s --> only has n=%d traces --> can't rotate" % (
            sta, len(trs)))

    return st_new
Beispiel #7
0
def calculate_energy_from_flux(cat,
                               inventory,
                               vp,
                               vs,
                               rho=2700.,
                               use_sdr_rad=False,
                               use_water_level=False,
                               rad_min=0.2):
    fname = 'calculate_energy_from_flux'

    for event in cat:
        origin = event.preferred_origin() if event.preferred_origin() else \
        event.origins[0]

        use_sdr = False

        if use_sdr_rad and event.preferred_focal_mechanism() is not None:
            mech = event.preferred_focal_mechanism()
            np1 = event.preferred_focal_mechanism().nodal_planes.nodal_plane_1
            sdr = (np1.strike, np1.dip, np1.rake)
            use_sdr = True

        # for phase in ['P', 'S']:
        # for arr in [x for x in arrivals if x.phase == phase]:

        P_energy = []
        S_energy = []

        for arr in origin.arrivals:
            pk = Pick(arr.get_pick())
            # try:
            sta = pk.get_sta()
            sta_response = inventory.select(sta)
            # except AttributeError:
            #     logger.warning(
            #         f'Cannot get station for arrival "{arr.resource_id}"'
            #         f' for event "{event.resource_id}".')

            # continue
            phase = arr.phase

            if phase.upper() == 'P':
                velocity = vp.interpolate(sta_response.loc)
                rad_pat = 4 / 15

            elif phase.upper() == 'S':
                velocity = vs.interpolate(sta_response.loc)
                rad_pat = 2 / 5

            # could check for arr.hypo_dist_in_m here but it's almost identical
            R = arr.distance

            # MTH: Setting preferred flux = vel_flux_Q = attenuation tstar
            # corrected flux
            flux = 0

            if arr.vel_flux_Q is not None:
                flux = arr.vel_flux_Q
                logger.info("%s: vel_flux_Q exists in [%s], using this for "
                            "energy, arr for sta:%s" % (fname, phase, sta))
            elif arr.vel_flux is not None:
                flux = arr.vel_flux
                logger.info("%s: vel_flux exists in [%s], using this for "
                            "energy, arr for sta:%s" % (fname, phase, sta))
            else:
                logger.info("%s: No vel_flux set for arr sta:%s pha:%s --> "
                            "skip energy calc" % (fname, sta, phase))

                continue

            energy = (4. * np.pi * R**2) * rho * velocity * flux

            scale = 1.

            if use_sdr:
                if arr.get('takeoff_angle', None) and arr.get('azimuth', None):
                    takeoff_angle = arr.takeoff_angle
                    takeoff_azimuth = arr.azimuth
                    strike = sdr[0]
                    dip = sdr[1]
                    rake = sdr[2]
                    rad = double_couple_rad_pat(takeoff_angle,
                                                takeoff_azimuth,
                                                strike,
                                                dip,
                                                rake,
                                                phase=phase)

                    if use_water_level and np.abs(rad) < rad_min:
                        rad = rad_min

                    scale = rad_pat / rad**2

            energy *= scale

            arr.energy = energy

            if phase == 'P':
                P_energy.append(energy)
            else:
                S_energy.append(energy)

        if not P_energy:
            P_energy = [0]
            logger.warning('No P energy measurements. The P-wave energy will '
                           'be set to 0, the total energy will not include '
                           'the P-wave energy. This will bias the total '
                           'energy value')
        if not S_energy:
            S_energy = [0]
            logger.warning(
                'No S energy measurements. The S-wave energy will be set to '
                '0, the total energy will not include the P-wave energy. '
                'This will bias the total energy value')

        energy_p = np.median(P_energy)
        energy_s = np.median(S_energy)
        E = energy_p + energy_s

        nvals = len(S_energy) + len(P_energy)
        comment = 'Energy [N-m] calculated from sum of median P + median S ' \
                  'energy'
        comment_ep = '"Ep":{}, "std_Ep":{}'.format(np.median(P_energy),
                                                   np.std(P_energy))
        comment_ep = '{' + comment_ep + '}'
        comment_es = '"Es":{}, "std_Es":{}'.format(np.median(S_energy),
                                                   np.std(S_energy))
        comment_es = '{' + comment_es + '}'

        if E > 0:
            # Note: this is not a "mag" (there is no log10).
            #       Just using magnitude class to hold it in quakeml

            energy_mag = Magnitude(
                origin_id=origin.resource_id,
                mag=E,
                magnitude_type='E',
                station_count=nvals,
                evaluation_mode='automatic',
                comments=[
                    Comment(text=comment),
                    Comment(text=comment_ep),
                    Comment(text=comment_es)
                ],
            )
            energy_mag.energy_p_joule = energy_p
            energy_mag.energy_s_joule = energy_s
            energy_mag.energy_joule = E

            event.magnitudes.append(energy_mag)

        else:
            logger.warning("%s: Calculated val of Energy E=[%s] nS=%d nP=%d "
                           "is not fit to keep!" %
                           (fname, E, nvals, len(P_energy)))
            logger.warning("%s: Energy mag not written to Quakeml" % fname)

    return cat
Beispiel #8
0
def calc_magnitudes_from_lambda(cat,
                                vp=5300,
                                vs=3500,
                                density=2700,
                                P_or_S='P',
                                use_smom=False,
                                use_sdr_rad=False,
                                use_free_surface_correction=False,
                                min_dist=20.,
                                **kwargs):
    """
    Calculate the moment magnitude at each station from lambda,
      where lambda is either:
        'dis_pulse_area' (use_smom=False) - calculated by integrating arrival
            displacement pulse in time
        'smom' (use_smom=True) - calculated by fiting Brune spectrum to
            displacement spectrum in frequency
    """

    fname = 'calc_magnitudes_from_lambda'

    # Don't loop over event here, do it in the calling routine
    #   so that vp/vs can be set for correct source depth
    event = cat[0]
    origin = event.preferred_origin() if event.preferred_origin() else \
        event.origins[0]
    ev_loc = origin.loc
    origin_id = origin.resource_id

    rad_P, rad_S = 0.52, 0.63

    if P_or_S == 'P':
        v = vp
        rad = rad_P
        mag_type = 'Mw_P'
    else:
        v = vs
        rad = rad_S
        mag_type = 'Mw_S'

    if use_smom:
        magnitude_comment = 'station magnitude measured in frequeny-domain (' \
                            'smom)'
        lambda_key = 'smom'
    else:
        magnitude_comment = 'station magnitude measured in time-domain (' \
                            'dis_pulse_area)'
        lambda_key = 'dis_pulse_area'

    if use_free_surface_correction and np.abs(ev_loc[2]) > 0.:
        logger.warning("%s: Free surface correction requested for event ["
                       "h=%.1f] > 0" % (fname, ev_loc[2]))

    if use_sdr_rad and 'sdr' not in kwargs:
        logger.warning("%s: use_sdr_rad requested but NO [sdr] given!" % fname)

    station_mags = []
    Mw_list = []

    Mw_P = []

    arrivals = [
        arr for arr in event.preferred_origin().arrivals if arr.phase == P_or_S
    ]

    for arr in arrivals:

        try:
            pk = arr.pick_id.get_referred_object()
            sta = pk.waveform_id.station_code
            cha = pk.waveform_id.channel_code
            net = pk.waveform_id.network_code
        except AttributeError:
            logger.warning('Missing data on arrival', exc_info=True)

            continue

        fs_factor = 1.

        if use_free_surface_correction:
            if arr.get('inc_angle', None):
                inc_angle = arr.inc_angle
                fs_factor = free_surface_displacement_amplification(
                    inc_angle, vp, vs, incident_wave=P_or_S)

                # MTH: Not ready to implement this.  The reflection coefficients
                #      are expressed in x1,x2,x3 coords
                # print("inc_angle:%.1f x1:%.1f x3:%.1f" % (inc_angle, fs_factor[0], fs_factor[2]))
                # MTH: The free surface corrections are returned as <x1,x2,x3>=<
                fs_factor = 1.
            else:
                logger.warning("%s: sta:%s cha:%s pha:%s: inc_angle NOT set "
                               "in arrival dict --> use default" %
                               (fname, sta, cha, arr.phase))

        if use_sdr_rad and 'sdr' in kwargs:
            strike, dip, rake = kwargs['sdr']

            if arr.get('takeoff_angle', None) and arr.get('azimuth', None):
                takeoff_angle = arr.takeoff_angle
                takeoff_azimuth = arr.azimuth
                rad = double_couple_rad_pat(takeoff_angle,
                                            takeoff_azimuth,
                                            strike,
                                            dip,
                                            rake,
                                            phase=P_or_S)
                rad = np.abs(rad)
                logger.debug("%s: phase=%s rad=%f" % (fname, P_or_S, rad))
                magnitude_comment += ' radiation pattern calculated for (s,' \
                                     'd,r)= (%.1f,%.1f,%.1f) theta:%.1f ' \
                                     'az:%.1f pha:%s |rad|=%f' % \
                                     (strike, dip, rake, takeoff_angle,
                                      takeoff_azimuth,
                                      P_or_S, rad)
                # logger.info(magnitude_comment)
            else:
                logger.warnng(
                    "%s: sta:%s cha:%s pha:%s: "
                    "takeoff_angle/azimuth NOT set in arrival dict --> use default radiation pattenr"
                    % (fname, sta, cha, arr.phase))

        _lambda = getattr(arr, lambda_key)

        if _lambda is not None:

            M0_scale = 4. * np.pi * density * v**3 / (rad * fs_factor)

            # R  = np.linalg.norm(sta_dict['station'].loc -ev_loc) #Dist in meters

            # MTH: obspy arrival.distance = *epicentral* distance in degrees
            #   >> Add attribute hypo_dist_in_m to uquake arrival class
            #         to make it clear
            if arr.distance:
                R = arr.distance
            else:
                R = arr.hypo_dist_in_m

            if R >= min_dist:

                M0 = M0_scale * R * np.abs(_lambda)
                Mw = 2. / 3. * np.log10(M0) - 6.033
                # print("MTH: _lambda=%g R=%.1f M0=%g" % (np.abs(_lambda), R, M0))

                Mw_list.append(Mw)

                station_mag = StationMagnitude(
                    origin_id=origin_id,
                    mag=Mw,
                    station_magnitude_type=mag_type,
                    comments=[Comment(text=magnitude_comment)],
                    waveform_id=WaveformStreamID(network_code=net,
                                                 station_code=sta,
                                                 channel_code=cha))
                station_mags.append(station_mag)

            else:
                logger.info("arrival sta:%s pha:%s dist=%.2f < min_dist("
                            "=%.2f) --> Skip" %
                            (fname, sta, arr.phase, R, min_dist))

        # else:
        # logger.warning("arrival sta:%s cha:%s arr pha:%s lambda_key:%s is NOT SET --> Skip" \
        # % (sta, cha, arr.phase, lambda_key))

    logger.info(
        "nmags=%d avg:%.1f med:%.1f std:%.1f" %
        (len(Mw_list), np.mean(Mw_list), np.median(Mw_list), np.std(Mw_list)))

    return np.median(Mw_list), station_mags