Beispiel #1
0
 def test_amplitude(self):
     """
     Test calculating amplitudes
     """
     plotit = True
     datafile = str(self.data_path / "20190519T060917_MONA.mseed")
     respfile = str(self.data_path / "SPOBS2_resp.json")
     stream = obspy_read(datafile, 'MSEED')
     wid = WaveformStreamID(network_code=stream[0].stats.network,
                            station_code=stream[0].stats.station,
                            channel_code=stream[0].stats.channel)
     Ppick = Pick(time=UTCDateTime('2019-05-19T06:09:48.83'),
                  phase_hint='P',
                  waveform_id=wid)
     Spick = Pick(time=UTCDateTime('2019-05-19T06:09:51.52'),
                  phase_hint='S',
                  waveform_id=wid)
     la = LocalAmplitude(stream, [Ppick, Spick], respfile, 'JSON_PZ')
     wood_calc, _ = la.get_iaml(method='wood_calc')
     wood_est, _ = la.get_iaml(method='wood_est')
     raw, _ = la.get_iaml(method='raw_disp')
     # Values obtained when response was mis-interpreted as nm/s
     # self.assertAlmostEqual(amp_wood_calc.generic_amplitude, 1097.55509106/1e9)
     # self.assertAlmostEqual(amp_wood_calc.period, 0.112)
     self.assertAlmostEqual(wood_calc.generic_amplitude * 1e9, 378.3697813)
     self.assertAlmostEqual(wood_calc.period, 0.12)
     self.assertAlmostEqual(wood_est.generic_amplitude * 1e9, 140.490154756)
     self.assertAlmostEqual(wood_est.period, 0.032)
     self.assertAlmostEqual(raw.generic_amplitude * 1e9, 460.35018097)
     self.assertAlmostEqual(raw.period, 0.112)
Beispiel #2
0
 def test_nordic_write(self):
     """
     Test calculating amplitudes
     """
     compare_file = str(self.data_path / "test.nordic")
     otime = UTCDateTime('2019-05-19T06:09:48')
     wid = WaveformStreamID(network_code='4G', station_code='STAT')
     wid.channel_code = 'SHZ'
     Ppick = Pick(time=otime + 2.2,
                  phase_hint='P',
                  waveform_id=wid,
                  evaluation_mode='automatic',
                  time_errors=QuantityError(0.01))
     Parrival = Arrival(pick_id=Ppick.resource_id, time_weight=0)
     wid.channel_code = 'SH1'
     Spick = Pick(time=otime + 3.5,
                  phase_hint='S',
                  waveform_id=wid,
                  evaluation_mode='automatic',
                  time_errors=QuantityError(0.05))
     wid.channel_code = 'SH2'
     Sarrival = Arrival(pick_id=Spick.resource_id, time_weight=1)
     Apick = Pick(time=otime + 4.0,
                  phase_hint='IAML',
                  waveform_id=wid,
                  evaluation_mode='automatic',
                  time_errors=QuantityError(0.1))
     Amp = Amplitude(generic_amplitude=410.e-9,
                     type='IAML',
                     unit='m',
                     period=0.22,
                     magnitude_hint='ML',
                     category='period',
                     pick_id=Apick.resource_id,
                     waveform_id=Apick.waveform_id)
     picks = [Ppick, Spick, Apick]
     amps = [Amp]
     arrivals = [Parrival, Sarrival]
     PSPicker.save_nordic_event(picks,
                                otime,
                                '.',
                                'test.nordic',
                                amps,
                                arrivals=arrivals,
                                debug=False)
     self.assertTextFilesEqual('test.nordic',
                               compare_file,
                               ignore_lines=[1])
     Path("test.nordic").unlink()
     for p in Path(".").glob('run_*.log'):
         p.unlink()
Beispiel #3
0
    def to_obspy(self, channel_maps, quality_thresholds=None):
        """
        Return obspy Pick and Arrival

        Arrival is None if there is no pick weight
        :param channel_maps: {station: ChannelMap object}
        :param quality_thresholds: list of 4 signal-to-noise ratio thresholds
            for pick quality, from lowest [quality=3] to highest [quality=0]
        :parm sampling_rate: sampling rate (used to estimate timing error)
        """
        c_map = channel_maps[self.station]
        if self.phase_guess == 'P':
            id = WaveformStreamID(seed_string=c_map.P_write_seed_string)
            phase_hint = c_map.P_write_phase
            log(f'{phase_hint}: {id.get_seed_string()}', 'debug')
        elif self.phase_guess == 'S':
            id = WaveformStreamID(seed_string=c_map.S_write_seed_string)
            phase_hint = c_map.S_write_phase
            log(f'{phase_hint}: {id.get_seed_string()}', 'debug')
        else:
            raise ValueError("phase guess '{self.phase_guess}' not 'P' or 'S'")
        self.weight = self._get_weight(quality_thresholds)
        time_errors = self._time_error()
        pick = Pick(time=self.time,
                    time_errors=time_errors,
                    waveform_id=id,
                    phase_hint=phase_hint,
                    evaluation_mode='automatic',
                    evaluation_status='preliminary')
        if self.weight is not None:
            return pick, Arrival(pick_id=pick.resource_id,
                                 time_weight=self.weight)
        else:
            return pick, None
Beispiel #4
0
def make_pick(pick_str, origin_time):
    """ Creates an ObsPy Pick object from a line of STP
    phase output.

    Sample pick_str:
    CI    CLC HHZ --   35.8157  -117.5975   775.0 P c. i  1.0    6.46   1.543
    """

    fields = pick_str.split()
    if len(fields) != 13:
        raise Exception('Invalid STP phase output')

    new_pick = Pick()
    (net, sta, chan, loc) = fields[:4]
    new_pick.waveform_id = WaveformStreamID(network_code=net,
                                            station_code=sta,
                                            channel_code=chan,
                                            location_code=loc)

    # Determine polarity from first motion.
    polarity = POLARITY_MAPPING[fields[8][0]]
    if polarity == '':
        polarity = POLARITY_MAPPING[fields[8][1]]
    if polarity != '':
        new_pick.polarity = polarity

    # Determine signal onset.
    if fields[9] == 'i':
        new_pick.onset = 'impulsive'
    elif fields[9] == 'e':
        new_pick.onset = 'emergent'
    else:
        new_pick.onset = 'questionable'

    # Determine time error from STP quality.
    # Use Jiggle standard and assume sample rate of 100 sps.
    quality = float(fields[10])
    if quality == 0.0:
        new_pick.time_errors = QuantityError(lower_uncertainty=0.03)
    elif quality <= 0.3:
        new_pick.time_errors = QuantityError(upper_uncertainty=0.03)
    elif quality <= 0.5:
        new_pick.time_errors = QuantityError(upper_uncertainty=0.02)
    elif quality <= 0.8:
        new_pick.time_errors = QuantityError(upper_uncertainty=0.01)
    elif quality == 1.0:
        new_pick.time_errors = QuantityError(upper_uncertainty=0.0)

    # Determine pick time.
    offset = float(fields[12])
    new_pick.time = origin_time + offset

    return new_pick
Beispiel #5
0
def get_picks_from_pdf(trace, height=0.5, distance=100):
    start_time = trace.stats.starttime
    peaks, properties = find_peaks(trace.pdf, height=height, distance=distance)

    picks = []
    for p in peaks:
        if p:
            time = start_time + p / trace.stats.sampling_rate
            phase_hint = "P"
            pick = Pick(time=time, phase_hint=phase_hint)
            picks.append(pick)

    return picks
Beispiel #6
0
 def test_amplitude(self):
     """
     Test calculating amplitudes
     """
     datafile = os.path.join(self.testing_path,
                             "20190519T060917_MONA.mseed")
     respfile = os.path.join(self.testing_path, "SPOBS2_response.json")
     stream = obspy_read(datafile, 'MSEED')
     wid = WaveformStreamID(network_code=stream[0].stats.network,
                            station_code=stream[0].stats.station,
                            channel_code=stream[0].stats.channel)
     Ppick = Pick(time=UTCDateTime('2019-05-19T06:09:48.83'),
                  phase_hint='P',
                  waveform_id=wid)
     Spick = Pick(time=UTCDateTime('2019-05-19T06:09:51.52'),
                  phase_hint='S',
                  waveform_id=wid)
     obj = LocalAmplitude(stream, [Ppick, Spick], respfile, 'JSON_PZ')
     amp_wood_calc, _ = obj.get_iaml(plot=False, method='wood_calc')
     amp_wood_est, _ = obj.get_iaml(plot=False, method='wood_est')
     amp_raw, _ = obj.get_iaml(plot=False, method='raw_disp')
     self.assertAlmostEqual(amp_wood_calc.generic_amplitude,
                            1097.555091056036)
     self.assertAlmostEqual(amp_wood_calc.period, 0.112)
Beispiel #7
0
def get_picks_from_pdf(trace, height=0.5, distance=100):
    start_time = trace.stats.starttime
    peaks, properties = find_peaks(trace.pdf, height=height, distance=distance)

    picks = []
    for p in peaks:
        if p:
            time = start_time + p / trace.stats.sampling_rate
            phase_hint = "P"
            pick = Pick(time=time, phase_hint=phase_hint)
            pick.waveform_id = WaveformStreamID(
                network_code=trace.stats.network,
                station_code=trace.stats.station,
                location_code=trace.stats.channel,
                channel_code=trace.stats.location)
            picks.append(pick)

    return picks
Beispiel #8
0
    def get_iaml(self, plot=False, method='wood_calc'):
        """
        get IAML amplitude and associated pick
        From IAPEI CoSOI 2013 Working Group recommentations:
        "ML = log10(A) + 1.11log10(R) + 0.00189*R - 2.09
        where
        A = maximum *trace* amplitude in *nm* that is measured on output from
            a *horizontal-component* instrument that is filtered so that the
            response of the seismograph/filter system replicates that of a
            *Wood-Anderson standard seismograph* but with a
            static magnification of 1
        R = *hypocentral distance in km*, typically less than 1000 km

        the constant -2.09 is based on an experimentally determined static
        magnification of the Wood-Anderson of 2080, rather than the theoretical
        magnification of 2800.

        The amplitudes used in the magnitude formulas ... are in most
        circumstances to be measured as one-half the maximum deflection of the
        seismogram trace, peak-to-adjacent- trough or trough-to-adjacent-peak,
        where peak and trough are separated by one crossing of the zero-line:
        the measurement is sometimes described as “one-half peak-to-peak
        amplitude.” None of the magnitude formulas presented in this article
        are intended to be used with the full peak-to-trough deflection as
        the amplitude.

        WA displacement zeros = [(0+0j), (0+0j)]
        WA displacement poles = [(-5.49779 - 5.60886j), (-5.49779 + 5.60886j)]
        WA seismometer free period = 0.8 s
        WA damping constant = 0.7
        A0 = 0.97866 @ 4 Hz
        :param plot: plot the result
        :param method:
            "wood_calc": calculate amplitude on the signal transformed to
                         simulate a unity gain Wood-Anderson filter
            "wood_est": calculate amplitude on the original signal and use
                        obspy.signal.invsim.estimate_wood_anderson_amplitude()
            "raw_disp": calculate amplitude on the signal transformed to
                        displacement in nm
        :returns: amplitude in meters (type=IAML), associated Pick
        :rtype: ~class obspy.core.event.magnitude.Amplitude,
                ~class obspy.core.event.origin.Pick
        """
        if self.ref_pick is None:
            log('No ref_pick!', 'debug')
            return None
        assert method in ['wood_calc', 'wood_est', 'raw_disp']

        signal = self.traces.copy()
        # do all work in nm
        paz_simulate, paz_simulate_obspy = None, None
        paz_remove = self.paz.copy()
        if method == 'wood_est':
            paz_remove.input_units = 'm/s'
            plot_units = 'Original (counts)'
        else:
            if method == 'wood_calc':
                paz_simulate = PAZ(poles=[(-5.49779 - 5.60886j),
                                          (-5.49779 + 5.60886j)],
                                   zeros=[(0 + 0j), (0 + 0j)],
                                   ref_gain=1,
                                   ref_freq=4.0,
                                   input_units='nm',
                                   output_units='counts')
                plot_units = 'Wood-And (nm)'
                paz_simulate_obspy = paz_simulate.to_obspy()
            elif method == 'raw_disp':
                paz_simulate = None
                paz_remove.input_units = 'nm'
                plot_units = 'Disp (nm)'
            for tr in signal:
                tr.simulate(paz_remove=paz_remove.to_obspy(),
                            paz_simulate=paz_simulate_obspy,
                            water_level=60.0)
        amp = pk2pk(signal, self.win_start, self.win_end)
        if amp is None:
            return None, None
        if method == 'wood_est':
            amp.value = estimate_wood_anderson_amplitude(
                paz_remove.to_obspy(), amp.value, amp.period)
            # estimate...() gives displacement amplitude ON a Wood-Anderson
            # seismometer, divide by 2080 then multiply by 1e6 to get ground
            # motion in nm?
            amp.value /= 2080. / 1e6
        if plot:
            self.plot(signal, amp, plot_units)
        waveform_id = self.ref_pick.waveform_id
        waveform_id.channel_code = amp.channel
        pick = Pick(time=amp.time,
                    waveform_id=waveform_id,
                    method_id='ps_picker',
                    phase_hint='IAML',
                    evaluation_mode='automatic',
                    evaluation_status='preliminary')
        obspy_amp = Amplitude(generic_amplitude=amp.value / (2. * 1.e9),
                              type='IAML',
                              unit='m',
                              period=amp.period,
                              magnitude_hint='ML',
                              category='period',
                              pick_id=pick.resource_id,
                              waveform_id=pick.waveform_id)
        return obspy_amp, pick
    def get_iaml(self,
                 plot=False,
                 method='wood_calc',
                 pre_filt=(0.005, 0.006, 30.0, 35.),
                 verbose=False):
        """
        get IAML amplitude and associated pick
        From IAPEI CoSOI 2013 Working Group recommentations:
        "ML = log10(A) + 1.11log10(R) + 0.00189*R - 2.09
        where
        A = maximum *trace* amplitude in *nm* that is measured on output from
            a *horizontal-component* instrument that is filtered so that the
            response of the seismograph/filter system replicates that of a
            *Wood-Anderson standard seismograph* but with a
            static magnification of 1
        R = *hypocentral distance in km*, typically less than 1000 km

        the constant -2.09 is based on an experimentally determined static
        magnification of the Wood-Anderson of 2080, rather than the theoretical
        magnification of 2800.

        The amplitudes used in the magnitude formulas ... are in most
        circumstances to be measured as one-half the maximum deflection of the
        seismogram trace, peak-to-adjacent- trough or trough-to-adjacent-peak,
        where peak and trough are separated by one crossing of the zero-line:
        the measurement is sometimes described as “one-half peak-to-peak
        amplitude.” None of the magnitude formulas presented in this article
        are intended to be used with the full peak-to-trough deflection as
        the amplitude.

        WA displacement zeros = [(0+0j), (0+0j)]
        WA displacement poles = [(-5.49779 - 5.60886j), (-5.49779 + 5.60886j)]
        WA seismometer free period = 0.8 s
        WA damping constant = 0.7
        A0 = 0.97866 @ 4 Hz
        
        Arguments:
            plot (bool): plot the result
            method (str): method used to calculate the amplitude.  Values are:
                "wood_calc": use the signal transformed to simulate a unity
                             gain Wood-Anderson filter
                "raw_disp": use the signal transformed to displacement.  For
                            periods > 0.25s this value can be significantly
                            higher than that given by wood_calc
                "wood_est": use the digital signal and obspy's
                            estimate_wood_anderson_amplitude(). Can be
                            trouble if the original signal was not proportional
                            to displacement
            pre_filt (tuple): pre_filter to apply to trace.simulate() to
                              prevent amplifying noise.

        Returns:
            tuple containing:
                (obspy Amplitude): amplitude in meters, type=IAML
                (obspy Pick): pick associated with the amplitude
        """
        if self.ref_pick is None:
            log('No ref_pick!', 'debug')
            return None
        assert method in ['wood_calc', 'wood_est', 'raw_disp']

        signal = self.traces.copy()
        # do all work in nm
        paz_simulate, paz_simulate_obspy = None, None
        paz_remove = self.paz.copy()
        # define a filter band to prevent amplifying noise during the deconvolution
        # print(f'{str(self.paz)=}')
        # print(f'{str(paz_remove)=}')
        if method == 'wood_est':
            paz_remove.input_units = 'm/s'
            plot_units = 'Original (counts)'
        else:
            # obspy PAZ has no units information, make displacement
            # paz_remove.input_units = 'nm'
            paz_remove_obspy = paz_remove.to_obspy()
            if method == 'wood_calc':
                # From Bormann & Dewey 2014, WA is flat w.r.t. displacement
                paz_simulate = PAZ.from_refgain(1,
                                                poles=[(-5.49779 - 5.60886j),
                                                       (-5.49779 + 5.60886j)],
                                                zeros=[(0 + 0j), (0 + 0j)],
                                                ref_freq=4.0,
                                                input_units='nm',
                                                output_units='counts')
                plot_units = 'WA (nm)'
                paz_simulate_obspy = paz_simulate.to_obspy()
            elif method == 'raw_disp':
                paz_simulate_obspy = None
                plot_units = 'Disp (nm)'
            for tr in signal:
                tr.simulate(paz_remove=paz_remove_obspy,
                            paz_simulate=paz_simulate_obspy,
                            pre_filt=pre_filt,
                            water_level=60.0)
        amp = pk2pk(signal, self.win_start, self.win_end)
        amp.value /= 2  # Convert to zero-to-peak
        # print(f'{str(self.paz)=}')
        # print(f'{paz_remove_obspy=}')
        # print(f'{str(paz_remove)=}')
        if amp is None:
            return None, None
        if method == 'wood_est':
            # simulated zero to peak disp amplitude on WA seismometer(mm)
            amp.value = estimate_wood_anderson_amplitude(
                paz_remove.to_obspy(), 2 * amp.value, amp.period)
            # Divide by 2080 then multiply by 1e6 to get ground motion in nm?
            # amp.value /= 2080./1e6
            # Divide by 2080 then remove seismometer gain?
            amp.value /= 2080.  # Remove obspy's WA seismometer sensitivity
            amp.value *= 1e6  # convert nm
        # print(f'{plot=}')
        if plot:
            # print(amp)
            self.plot(signal, amp, method, plot_units)
            plt.show()
        waveform_id = self.ref_pick.waveform_id
        waveform_id.channel_code = amp.channel
        pick = Pick(time=amp.time,
                    waveform_id=waveform_id,
                    method_id='ps_picker',
                    phase_hint='IAML',
                    evaluation_mode='automatic',
                    evaluation_status='preliminary')
        obspy_amp = Amplitude(generic_amplitude=amp.value / 1.e9,
                              type='IAML',
                              unit='m',
                              period=amp.period,
                              magnitude_hint='ML',
                              category='period',
                              pick_id=pick.resource_id,
                              waveform_id=pick.waveform_id)
        if verbose:
            print('{} IAML = {:.4g} nm at {}s'.format(
                waveform_id.get_seed_string(), amp.value, amp.period))
        return obspy_amp, pick