Ejemplo n.º 1
0
    def reject_on_prominence_of_central_peak(self):
        """
        Equivalent to reject_on_prominence() in the original flexwin code.
        """
        # The fine tuning constant is often set to 0. Nothing to do in this
        # case as all windows will then pass the criteria by definition.
        if not self.config.c_2:
            return self.windows

        def filter_windows_maximum_prominence(win):
            smaller_troughs = self.troughs[self.troughs < win.center]
            larger_troughs = self.troughs[self.troughs > win.center]

            if not len(smaller_troughs) or not len(larger_troughs):
                return False

            left = self.stalta[smaller_troughs[-1]]
            right = self.stalta[larger_troughs[0]]
            center = self.stalta[win.center]
            delta_left = center - left
            delta_right = center - right

            if (delta_left < self.config.c_2 * center) or \
                    (delta_right < self.config.c_2 * center):
                return False
            return True

        windows = list(filter(filter_windows_maximum_prominence, self.windows))
        self.separate_rejects(windows, "prominence")

        logger.info("Prominence of central peak rejection retained "
                    "%i windows." % len(self.windows))
Ejemplo n.º 2
0
    def initial_window_selection(self):
        """
        Find all possible windows. This is equivalent to the setup_M_L_R()
        function in flexwin.
        """
        for peak in self.peaks:
            # only continue if there are available minima on either side
            if peak <= self.troughs[0] or peak >= self.troughs[-1]:
                continue
            # only continue if this maximum is above the water level
            if self.stalta[peak] <= self.config.stalta_waterlevel[peak]:
                continue
            smaller_troughs = self.troughs[self.troughs < peak]
            larger_troughs = self.troughs[self.troughs > peak]

            for left, right in itertools.product(smaller_troughs,
                                                 larger_troughs):
                self.windows.append(
                    Window(left=left,
                           right=right,
                           center=peak,
                           channel_id=self.observed.id,
                           time_of_first_sample=self.synthetic.stats.starttime,
                           dt=self.observed.stats.delta,
                           min_period=self.config.min_period,
                           weight_function=self.config.window_weight_fct))

        logger.info("Initial window selection yielded %i possible windows." %
                    len(self.windows))
Ejemplo n.º 3
0
    def schedule_weighted_intervals(self):
        """
        Run the weighted interval scheduling.
        """
        self.windows = schedule_weighted_intervals(self.windows)

        logger.info("Weighted interval schedule optimization retained %i "
                    "windows." % len(self.windows))
Ejemplo n.º 4
0
 def reject_windows_based_on_minimum_length(self):
     """
     Reject windows smaller than the minimal window length.
     """
     windows = list(
         filter(lambda x: (x.right - x.left) >= self.minimum_window_length,
                self.windows))
     self.separate_rejects(windows, "min_length")
     logger.info("Rejection based on minimum window length retained %i "
                 "windows." % len(self.windows))
Ejemplo n.º 5
0
 def calculate_ttimes(self):
     """
     Calculate theoretical travel times. Only call if station and event
     information is available!
     """
     dist_in_deg = obspy.geodetics.locations2degrees(
         self.station.latitude, self.station.longitude, self.event.latitude,
         self.event.longitude)
     tts = self.taupy_model.get_travel_times(
         source_depth_in_km=self.event.depth_in_m / 1000.0,
         distance_in_degree=dist_in_deg)
     self.ttimes = [{"time": _i.time, "name": _i.name} for _i in tts]
     logger.info("Calculated travel times.")
Ejemplo n.º 6
0
    def reject_based_on_data_fit_criteria(self):
        """
        Rejects windows based on similarity between data and synthetics.
        """
        # First calculate the criteria for all remaining windows.
        for win in self.windows:
            win._calc_criteria(self.observed.data, self.synthetic.data)

        def reject_based_on_time_shift(win):
            tshift_min = self.config.tshift_reference - \
                self.config.tshift_acceptance_level[win.center]
            tshift_max = self.config.tshift_reference + \
                self.config.tshift_acceptance_level[win.center]

            if not (tshift_min < win.cc_shift * self.observed.stats.delta <
                    tshift_max):
                logger.debug("Window rejected due to time shift: %f" %
                             win.cc_shift)
                return False
            return True

        def reject_based_on_dlna(win):
            dlnA_min = self.config.dlna_reference - \
                self.config.dlna_acceptance_level[win.center]
            dlnA_max = self.config.dlna_reference + \
                self.config.dlna_acceptance_level[win.center]

            if not (dlnA_min < win.dlnA < dlnA_max):
                logger.debug("Window rejected due to amplitude fit: %f" %
                             win.dlnA)
                return False
            return True

        def reject_based_on_cc_value(win):
            if win.max_cc_value < self.config.cc_acceptance_level[win.center]:
                logger.debug("Window rejected due to CC value: %f" %
                             win.max_cc_value)
                return False
            return True

        for func, tag in zip([
                reject_based_on_time_shift, reject_based_on_dlna,
                reject_based_on_cc_value
        ], ["tshift", "dlna", "cc"]):
            windows = list(filter(func, self.windows))
            self.separate_rejects(windows, tag)

        logger.info(
            "Rejection based on data fit criteria retained %i windows." %
            len(self.windows))
Ejemplo n.º 7
0
    def calculate_preliminiaries(self):
        """
        Calculates the envelope, STA/LTA and the finds the local extrema.
        """
        logger.info("Calculating envelope of synthetics.")
        self.synthetic_envelope = envelope(self.synthetic.data)
        logger.info("Calculating STA/LTA.")
        self.stalta = sta_lta(self.synthetic_envelope,
                              self.observed.stats.delta,
                              self.config.min_period)
        self.peaks, self.troughs = utils.find_local_extrema(self.stalta)

        if not len(self.peaks) and len(self.troughs):
            return

        if self.ttimes:
            offset = self.event.origin_time - self.observed.stats.starttime
            min_time = self.ttimes[0]["time"] - \
                self.config.max_time_before_first_arrival + offset
            min_idx = int(min_time / self.observed.stats.delta)

            dist_in_km = obspy.geodetics.calc_vincenty_inverse(
                self.station.latitude, self.station.longitude,
                self.event.latitude, self.event.longitude)[0] / 1000.0
            max_time = dist_in_km / self.config.min_surface_wave_velocity + \
                offset + self.config.max_period
            max_idx = int(max_time / self.observed.stats.delta)

            # Reject all peaks and troughs before the minimal allowed start
            # time and after the maximum allowed end time.
            first_trough, last_trough = self.troughs[0], self.troughs[-1]
            self.troughs = self.troughs[(self.troughs >= min_idx)
                                        & (self.troughs <= max_idx)]

            # If troughs have been removed, readd them add the boundaries.
            if len(self.troughs):
                if first_trough != self.troughs[0]:
                    self.troughs = np.concatenate([
                        np.array([min_idx], dtype=self.troughs.dtype),
                        self.troughs
                    ])
                if last_trough != self.troughs[-1]:
                    self.troughs = np.concatenate([
                        self.troughs,
                        np.array([max_idx], dtype=self.troughs.dtype)
                    ])
            # Make sure peaks are inside the troughs!
            min_trough, max_trough = self.troughs[0], self.troughs[-1]
            self.peaks = self.peaks[(self.peaks > min_trough)
                                    & (self.peaks < max_trough)]
Ejemplo n.º 8
0
    def reject_based_on_signal_to_noise_ratio(self):
        """
        Rejects windows based on their signal to noise amplitude ratio.
        """
        if self.config.noise_end_index is None:
            logger.warning("Cannot reject windows based on their signal to "
                           "noise ratio. Please give station and event "
                           "information or information about the temporal "
                           "range of the noise.")
            return

        noise = self.observed.data[self.config.noise_start_index:self.config.
                                   noise_end_index]

        # Very short source-receiver distances can sometimes produce 0 length
        # noise signals
        if not noise:
            logger.warning("pre-arrival noise could not be determined, "
                           "skipping rejection based on signal-to-noise ratio")
            return
        elif self.config.window_signal_to_noise_type == "amplitude":
            noise_amp = np.abs(noise).max()

            def filter_window_noise(win):
                win_signal = self.observed.data[win.left:win.right]
                win_noise_amp = np.abs(win_signal).max() / noise_amp
                if win_noise_amp < self.config.s2n_limit[win.center]:
                    return False
                return True
        elif self.config.window_signal_to_noise_type == "energy":
            noise_energy = np.sum(noise**2) / len(noise)

            def filter_window_noise(win):
                data = self.observed.data[win.left:win.right]
                win_energy = np.sum(data**2) / len(data)
                win_noise_amp = win_energy / noise_energy
                if win_noise_amp < self.config.s2n_limit[win.center]:
                    return False
                return True
        else:
            raise NotImplementedError

        windows = list(filter(filter_window_noise, self.windows))
        self.separate_rejects(windows, "s2n")

        logger.info("SN amplitude ratio window rejection retained %i windows" %
                    len(self.windows))
Ejemplo n.º 9
0
    def reject_on_minima_water_level(self):
        """
        Filter function rejecting windows whose internal minima are below the
        water level of the windows peak. This is equivalent to the
        reject_on_water_level() function in flexwin.
        """
        def filter_window_minima(win):
            waterlevel_midpoint = \
                self.config.c_0 * self.config.stalta_waterlevel[win.center]
            internal_minima = win._get_internal_indices(self.troughs)
            return not np.any(
                self.stalta[internal_minima] <= waterlevel_midpoint)

        windows = list(filter(filter_window_minima, self.windows))
        self.separate_rejects(windows, "water_level")

        logger.info("Water level rejection retained %i windows" %
                    len(self.windows))
Ejemplo n.º 10
0
    def check_data_quality(self):
        """
        Checks the data quality by estimating signal to noise ratios.
        """
        if self.config.noise_end_index is None:
            raise PyflexError(
                "Cannot check data quality as the noise end index is not "
                "given and station and/or event information is not "
                "available so the theoretical arrival times cannot be "
                "calculated.")

        noise = self.observed.data[self.config.noise_start_index:self.config.
                                   noise_end_index]
        signal = self.observed.data[self.config.signal_start_index:self.config.
                                    signal_end_index]

        noise_int = np.sum(noise**2) / len(noise)
        noise_amp = np.abs(noise).max()
        signal_int = np.sum(signal**2) / len(signal)
        signal_amp = np.abs(signal).max()

        # Calculate ratios.
        snr_int = signal_int / noise_int
        snr_amp = signal_amp / noise_amp

        if snr_int < self.config.snr_integrate_base:
            msg = ("Whole waveform rejected as the integrated signal to "
                   "noise ratio (%f) is above the threshold (%f)." %
                   (snr_int, self.config.snr_integrate_base))
            logger.warn(msg)
            warnings.warn(msg, PyflexWarning)
            return False

        if snr_amp < self.config.snr_max_base:
            msg = ("Whole waveform rejected as the signal to noise amplitude "
                   "ratio (%f) is above the threshold (%f)." %
                   (snr_amp, self.config.snr_max_base))
            logger.warn(msg)
            warnings.warn(msg, PyflexWarning)
            return False

        logger.info("Global SNR checks passed. Integrated SNR: %f, Amplitude "
                    "SNR: %f" % (snr_int, snr_amp))
        return True
Ejemplo n.º 11
0
    def reject_on_phase_separation(self):
        """
        Reject windows based on phase seperation. Equivalent to
        reject_on_phase_separation() in the original flexwin code.
        """
        def filter_phase_rejection(win):
            # Find the lowest minimum within the window.
            internal_minima = self.troughs[(self.troughs >= win.left)
                                           & (self.troughs <= win.right)]
            stalta_min = self.stalta[internal_minima].min()
            # find the height of the central maximum above this minimum value
            d_stalta_center = self.stalta[win.center] - stalta_min
            # Find all internal maxima.
            internal_maxima = self.peaks[(self.peaks >= win.left)
                                         & (self.peaks <= win.right) &
                                         (self.peaks != win.center)]

            for max_index in internal_maxima:
                # find height of current maximum above lowest minimum
                d_stalta = self.stalta[max_index] - stalta_min
                # find scaled time between current maximum and central maximum
                d_time = abs(win.center - max_index) * \
                    self.observed.stats.delta / self.config.min_period
                # find value of time decay function.
                # The paper has a square root in the numinator of the
                # exponent as well. Not the case here as it is not the case
                # in the original flexwin code.
                if (d_time >= self.config.c_3b):
                    f_time = np.exp(-(
                        (d_time - self.config.c_3b) / self.config.c_3b)**2)
                else:
                    f_time = 1.0
                # check condition
                if d_stalta > (self.config.c_3a * d_stalta_center * f_time):
                    break
            else:
                return True
            return False

        windows = list(filter(filter_phase_rejection, self.windows))
        self.separate_rejects(windows, "phase_sep")
        logger.info("Single phase group rejection retained %i windows" %
                    len(self.windows))
Ejemplo n.º 12
0
    def remove_duplicates(self):
        """
        Filter to remove duplicate windows based on left and right bounds.

        This function will also change the middle to actually be in the
        center of the window. This should result in better results for the
        following stages as lots of thresholds are evaluated at the center
        of a window.
        """
        new_windows = {}
        for window in self.windows:
            tag = (window.left, window.right)
            if tag not in new_windows:
                window.center = \
                    int(window.left + (window.right - window.left) / 2.0)
                new_windows[tag] = window
        self.windows = sorted(new_windows.values(), key=lambda x: x.left)
        logger.info("Removing duplicates retains %i windows." %
                    len(self.windows))
Ejemplo n.º 13
0
    def merge_windows(self):
        """
        Merge overlapping windows. Will also recalculate the data fit criteria.
        """
        # Sort by starttime.
        self.windows = sorted(self.windows, key=lambda x: x.left)
        windows = [self.windows.pop(0)]
        for right_win in self.windows:
            left_win = windows[-1]
            if (left_win.right + 1) < right_win.left:
                windows.append(right_win)
                continue
            left_win.right = right_win.right
        self.windows = windows

        for win in self.windows:
            # Recenter windows
            win.center = int(win.left + (win.right - win.left) / 2.0)
            # Recalculate criteria.
            win._calc_criteria(self.observed.data, self.synthetic.data)
        logger.info("Merging windows resulted in %i windows." %
                    len(self.windows))
Ejemplo n.º 14
0
        def curtail_window_length(win):
            time_decay_left = self.config.min_period * self.config.c_4a / dt
            time_decay_right = self.config.min_period * self.config.c_4b / dt
            # Find all internal maxima.
            internal_maxima = self.peaks[(self.peaks >= win.left)
                                         & (self.peaks <= win.right) &
                                         (self.peaks != win.center)]
            if len(internal_maxima) < 2:
                return win
            i_left = internal_maxima[0]
            i_right = internal_maxima[-1]

            delta_left = i_left - win.left
            delta_right = win.right - i_right

            # check condition
            if delta_left > time_decay_left:
                logger.info("Curtailing left")
                win.left = int(i_left - time_decay_left)
            if delta_right > time_decay_right:
                logger.info("Curtailing right")
                win.right = int(i_right + time_decay_right)
            return win
Ejemplo n.º 15
0
    def reject_on_traveltimes(self):
        """
        Reject based on traveltimes. Will reject windows containing only
        data before a minimum period before the first arrival and windows
        only containing data after the minimum allowed surface wave speed.
        Only call if station and event information is available!
        """
        dist_in_km = obspy.geodetics.calc_vincenty_inverse(
            self.station.latitude, self.station.longitude, self.event.latitude,
            self.event.longitude)[0] / 1000.0

        offset = self.event.origin_time - self.observed.stats.starttime

        min_time = self.ttimes[0]["time"] - self.config.min_period + offset
        max_time = dist_in_km / self.config.min_surface_wave_velocity + offset
        windows = [
            win for win in self.windows
            if (win.relative_endtime >= min_time) and (
                win.relative_starttime <= max_time)
        ]

        self.separate_rejects(windows, "traveltimes")
        logger.info("Rejection based on travel times retained %i windows." %
                    len(self.windows))
Ejemplo n.º 16
0
    def _parse_event_and_station(self):
        """
        Parse the event and station information.
        """
        # Parse the event.
        if self.event and not isinstance(self.event, Event):
            # It might be an ObsPy event catalog.
            if isinstance(self.event, obspy.core.event.Catalog):
                if len(self.event) != 1:
                    raise PyflexError("The event catalog must contain "
                                      "exactly one event.")
                self.event = self.event[0]
            # It might be an ObsPy event object.
            if isinstance(self.event, obspy.core.event.Event):
                if not self.event.origins:
                    raise PyflexError("Event does not contain an origin.")
                origin = self.event.preferred_origin() or self.event.origins[0]
                self.event = Event(latitude=float(origin.latitude),
                                   longitude=float(origin.longitude),
                                   depth_in_m=float(origin.depth),
                                   origin_time=origin.time)
            else:
                raise PyflexError("Could not parse the event. Unknown type.")

        # Parse the station information if it is an obspy inventory object.
        if isinstance(self.station, obspy.core.inventory.Inventory):
            net = self.observed.stats.network
            sta = self.observed.stats.station
            # Workaround for ObsPy 0.9.2 Newer version have a get
            # coordinates method...
            for network in self.station:
                if network.code == net:
                    break
            else:
                raise PyflexError("Could not find the network of the "
                                  "observed data in the inventory object.")
            for station in network:
                if station.code == sta:
                    break
            else:
                raise PyflexError("Could not find the station of the "
                                  "observed data in the inventory object.")
            self.station = Station(latitude=float(station.latitude),
                                   longitude=float(station.longitude))

        # Last resort, if either is not set, and the observed or synthetics
        # are sac files, get the information from there.
        if not self.station or not self.event:
            if hasattr(self.observed.stats, "sac"):
                tr = self.observed
                ftype = "observed"
            elif hasattr(self.synthetic.stats, "sac"):
                tr = self.synthetic
                ftype = "synthetic"
            else:
                return
            sac = tr.stats.sac
            values = (sac.evla, sac.evlo, sac.evdp, sac.stla, sac.stlo, sac.b)
            # Invalid value in sac.
            if -12345.0 in values:
                return
            if not self.station:
                self.station = Station(latitude=values[3], longitude=values[4])
                logger.info("Extracted station information from %s SAC file." %
                            ftype)
            if not self.event:
                self.event = Event(latitude=values[0],
                                   longitude=values[1],
                                   depth_in_m=values[2] * 1000.0,
                                   origin_time=self.observed.stats.starttime -
                                   values[5])
                logger.info("Extracted event information from %s SAC file." %
                            ftype)