Source code for obspy.core.util.misc

# -*- coding: utf-8 -*-
"""
Various additional utilities for ObsPy.

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

import inspect
import itertools
import math
import os
import platform
import shutil
import sys
import tempfile
import warnings
from contextlib import contextmanager
from subprocess import STDOUT, CalledProcessError, check_output

import numpy as np


# The following dictionary maps the first character of the channel_id to the
# lowest sampling rate this so called Band Code should be used for according
# to: SEED MANUAL p.124
# We use this e.g. in seishub.client.getWaveform to request two samples more on
# both start and end to cut to the samples that really are nearest to requested
# start/end time afterwards.

BAND_CODE = {'F': 1000.0,
             'G': 1000.0,
             'D': 250.0,
             'C': 250.0,
             'E': 80.0,
             'S': 10.0,
             'H': 80.0,
             'B': 10.0,
             'M': 1.0,
             'L': 1.0,
             'V': 0.1,
             'U': 0.01,
             'R': 0.0001,
             'P': 0.000001,
             'T': 0.0000001,
             'Q': 0.00000001}


[docs]def guess_delta(channel): """ Estimate time delta in seconds between each sample from given channel name. :type channel: str :param channel: Channel name, e.g. ``'BHZ'`` or ``'H'`` :rtype: float :return: Returns ``0`` if band code is not given or unknown. .. rubric:: Example >>> print(guess_delta('BHZ')) 0.1 >>> print(guess_delta('H')) 0.0125 >>> print(guess_delta('XZY')) # doctest: +SKIP 0 """ try: return 1. / BAND_CODE[channel[0]] except: msg = "No or unknown channel id provided. Specifying a channel id " + \ "could lead to better selection of first/last samples of " + \ "fetched traces." warnings.warn(msg) return 0
[docs]def score_at_percentile(values, per, limit=(), issorted=True): """ Calculates the score at the given per percentile of the sequence a. For example, the score at ``per=50`` is the median. If the desired quantile lies between two data points, we interpolate between them. If the parameter ``limit`` is provided, it should be a tuple (lower, upper) of two values. Values of ``a`` outside this (closed) interval will be ignored. .. rubric:: Examples >>> a = [1, 2, 3, 4] >>> score_at_percentile(a, 25) 1.75 >>> score_at_percentile(a, 50) 2.5 >>> score_at_percentile(a, 75) 3.25 >>> a = [6, 47, 49, 15, 42, 41, 7, 255, 39, 43, 40, 36, 500] >>> score_at_percentile(a, 25, limit=(0, 100)) 25.5 >>> score_at_percentile(a, 50, limit=(0, 100)) 40 >>> score_at_percentile(a, 75, limit=(0, 100)) 42.5 This function is taken from :func:`scipy.stats.score_at_percentile`. Copyright (c) Gary Strangman """ if limit: values = [v for v in values if limit[0] < v < limit[1]] if issorted: values = sorted(values) def _interpolate(a, b, fraction): return a + (b - a) * fraction idx = per / 100. * (len(values) - 1) if (idx % 1 == 0): return values[int(idx)] else: return _interpolate(values[int(idx)], values[int(idx) + 1], idx % 1)
[docs]def flat_not_masked_contiguous(a): """ Find contiguous unmasked data in a masked array along the given axis. This function is taken from :func:`numpy.ma.flatnotmasked_contiguous`. Copyright (c) Pierre Gerard-Marchant """ np.ma.extras.flatnotmasked_contiguous m = np.ma.getmask(a) if m is np.ma.nomask: return slice(0, a.size, None) i = 0 result = [] for (k, g) in itertools.groupby(m.ravel()): n = len(list(g)) if not k: result.append(slice(i, i + n)) i += n return result or None
[docs]def complexify_string(line): """ Converts a string in the form "(real, imag)" into a complex type. :type line: str :param line: String in the form ``"(real, imag)"``. :rtype: complex :return: Complex number. .. rubric:: Example >>> complexify_string("(1,2)") (1+2j) >>> complexify_string(" ( 1 , 2 ) ") (1+2j) """ temp = line.split(',') return complex(float(temp[0].strip()[1:]), float(temp[1].strip()[:-1]))
[docs]def to_int_or_zero(value): """ Converts given value to an integer or returns 0 if it fails. :param value: Arbitrary data type. :rtype: int .. rubric:: Example >>> to_int_or_zero("12") 12 >>> to_int_or_zero("x") 0 """ try: return int(value) except ValueError: return 0 # import numpy loadtxt and check if ndmin parameter is available
try: from numpy import loadtxt loadtxt(np.array([0]), ndmin=1) except TypeError: # otherwise redefine loadtxt def loadtxt(*args, **kwargs): """ Replacement for older numpy.loadtxt versions not supporting ndmin parameter. """ if 'ndmin' not in kwargs: return np.loadtxt(*args, **kwargs) # ok we got a ndmin param if kwargs['ndmin'] != 1: # for now we support only one dimensional arrays raise NotImplementedError('Upgrade your NumPy version!') del kwargs['ndmin'] dtype = kwargs.get('dtype', None) # lets get the data try: data = np.loadtxt(*args, **kwargs) except IOError as e: # raises in older versions if no data could be read if 'reached before encountering data' in str(e): # return empty array return np.array([], dtype=dtype) # otherwise just raise raise # ensures that an array is returned return np.atleast_1d(data)
[docs]def get_untracked_files_from_git(): """ Tries to return a list of files (absolute paths) that are untracked by git in the repository. Returns `None` if the system call to git fails. """ dir_ = os.path.abspath( os.path.dirname(inspect.getfile(inspect.currentframe()))) dir_ = os.path.dirname(os.path.dirname(os.path.dirname(dir_))) try: # Check that the git root directory is actually the ObsPy directory. p = check_output(['git', 'rev-parse', '--show-toplevel'], cwd=dir_, stderr=STDOUT) git_root_dir = p.decode().strip() if git_root_dir: git_root_dir = os.path.abspath(git_root_dir) if git_root_dir != dir_: raise ValueError('Git root directory (%s) does not match expected ' 'path (%s).' % (git_root_dir, dir_)) p = check_output(['git', 'status', '-u', '--porcelain'], cwd=dir_, stderr=STDOUT) stdout = p.decode().splitlines() files = [os.path.abspath(os.path.join(dir_, line.split()[1].strip())) for line in stdout if line.startswith("??")] except (OSError, CalledProcessError): return None return files
@contextmanager
[docs]def CatchOutput(): # noqa -> this name is IMHO okay for a context manager. """ A context manager that catches stdout/stderr/exit() for its scope. Always use with "with" statement. Does nothing otherwise. Based on: https://bugs.python.org/msg184312 >>> with CatchOutput() as out: # doctest: +SKIP ... os.system('echo "mystdout"') ... os.system('echo "mystderr" >&2') >>> print(out.stdout) # doctest: +SKIP mystdout >>> print(out.stderr) # doctest: +SKIP mystderr """ # Dummy class to transport the output. class Output(): pass out = Output() out.stdout = '' out.stderr = '' stdout_fd = sys.stdout.fileno() stderr_fd = sys.stderr.fileno() with tempfile.TemporaryFile(prefix='obspy-') as tmp_stdout: with tempfile.TemporaryFile(prefix='obspy-') as tmp_stderr: stdout_copy = os.dup(stdout_fd) stderr_copy = os.dup(stderr_fd) try: sys.stdout.flush() os.dup2(tmp_stdout.fileno(), stdout_fd) sys.stderr.flush() os.dup2(tmp_stderr.fileno(), stderr_fd) raised = False yield out except SystemExit: raised = True finally: sys.stdout.flush() os.dup2(stdout_copy, stdout_fd) os.close(stdout_copy) tmp_stdout.seek(0) out.stdout = tmp_stdout.read() sys.stderr.flush() os.dup2(stderr_copy, stderr_fd) os.close(stderr_copy) tmp_stderr.seek(0) out.stderr = tmp_stderr.read() if platform.system() == "Windows": out.stdout = out.stdout.replace(b'\r', b'') out.stderr = out.stderr.replace(b'\r', b'') if raised: raise SystemExit(out.stderr)
@contextmanager
[docs]def TemporaryWorkingDirectory(): # noqa --> this name is IMHO ok for a CM """ A context manager that changes to a temporary working directory. Always use with "with" statement. Does nothing useful otherwise. >>> with TemporaryWorkingDirectory(): # doctest: +SKIP ... os.system('echo "$PWD"') """ tempdir = tempfile.mkdtemp(prefix='obspy-') old_dir = os.getcwd() os.chdir(tempdir) try: yield finally: os.chdir(old_dir) shutil.rmtree(tempdir)
[docs]def factorize_int(x): """ Calculate prime factorization of integer. Could be done faster but faster algorithm have much more lines of code and this is fast enough for our purposes. https://stackoverflow.com/q/14550794 >>> factorize_int(1800004) [2, 2, 450001] >>> factorize_int(1800003) [3, 19, 23, 1373] """ if x == 1: return [1] factors, limit, check, num = [], int(math.sqrt(x)) + 1, 2, x for check in range(2, limit): while num % check == 0: factors.append(check) num /= check if num > 1: factors.append(int(num)) return factors
[docs]def get_window_times(starttime, endtime, window_length, step, offset, include_partial_windows): """ Function calculating a list of times making up equal length windows from within a given time interval. :param starttime: The start time of the whole time interval. :type starttime: :class:`~obspy.core.utcdatetime.UTCDateTime` :param endtime: The end time of the whole time interval. :type endtime: :class:`~obspy.core.utcdatetime.UTCDateTime` :param window_length: The length of each window in seconds. :type window_length: float :param step: The step between the start times of two successive windows in seconds. Can be negative if an offset is given. :type step: float :param offset: The offset of the first window in seconds relative to the start time of the whole interval. :type offset: float :param include_partial_windows: Determines if windows that are shorter then 99.9 % of the desired length are returned. :type include_partial_windows: bool """ if step > 0: end = endtime.timestamp - 0.001 * step else: # This minus is correct here due to the way the actual window times # are calculate later on. end = starttime.timestamp - 0.001 * abs(step) # Left sides of each window. indices = np.arange(start=starttime.timestamp + offset, stop=end, step=step, dtype=np.float64) if step > 0: # Generate all possible windows. windows = [(_i, min(_i + window_length, endtime.timestamp)) for _i in indices] else: # Generate all possible windows. windows = [(max(_i - window_length, starttime.timestamp), _i) for _i in indices] # Potentially remove partial windows not fulfilling the window length # criterion. if not include_partial_windows: windows = [_i for _i in windows if abs(_i[1] - _i[0]) > 0.999 * window_length] t = type(starttime) return [(t(_i[0]), t(_i[1])) for _i in windows]
[docs]class MatplotlibBackend(object): """ A helper class for switching the matplotlib backend. Can be used as a context manager to temporarily switch the backend or by using the :meth:`~MatplotlibBackend.switch_backend` staticmethod. The context manager has no effect when setting ``backend=None``. :type backend: str :param backend: Name of matplotlib backend to switch to. :type sloppy: bool :param sloppy: If ``True``, uses :func:`matplotlib.pyplot.switch_backend` and no warning will be shown if the backend was not switched successfully. If ``False``, additionally tries to use :func:`matplotlib.use` first and also shows a warning if the backend was not switched successfully. """
[docs] def __init__(self, backend, sloppy=True): self.temporary_backend = backend self.sloppy = sloppy import matplotlib self.previous_backend = matplotlib.get_backend()
[docs] def __enter__(self): if self.temporary_backend is None: return self.switch_backend(backend=self.temporary_backend, sloppy=self.sloppy)
[docs] def __exit__(self, exc_type, exc_val, exc_tb): # @UnusedVariable if self.temporary_backend is None: return self.switch_backend(backend=self.previous_backend, sloppy=self.sloppy)
@staticmethod
[docs] def switch_backend(backend, sloppy=True): """ Switch matplotlib backend. :type backend: str :param backend: Name of matplotlib backend to switch to. :type sloppy: bool :param sloppy: If ``True``, only uses :func:`matplotlib.pyplot.switch_backend` and no warning will be shown if the backend was not switched successfully. If ``False``, additionally tries to use :func:`matplotlib.use` first and also shows a warning if the backend was not switched successfully. """ import matplotlib # sloppy. only do a `plt.switch_backend(..)` if sloppy: import matplotlib.pyplot as plt plt.switch_backend(backend) else: # check if `matplotlib.use(..)` is emitting a warning try: with warnings.catch_warnings(record=True): warnings.simplefilter("error", UserWarning) matplotlib.use(backend) # if that's the case, follow up with `plt.switch_backend(..)` except UserWarning: import matplotlib.pyplot as plt plt.switch_backend(backend) # finally check if the switch was successful, # show a warning if not if matplotlib.get_backend().upper() != backend.upper(): msg = "Unable to change matplotlib backend to '%s'" % backend raise Exception(msg)
if __name__ == '__main__': import doctest doctest.testmod(exclude_empty=True)