'''
:copyright:
The SeisMIC development team (makus@gfz-potsdam.de).
:license:
EUROPEAN UNION PUBLIC LICENCE v. 1.2
(https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12)
:author:
Peter Makus (makus@gfz-potsdam.de)
Created: Monday, 5th July 2021 02:44:13 pm
Last Modified: Monday, 30th January 2023 02:26:32 pm
'''
from obspy.core.util import AttribDict
from obspy import UTCDateTime
[docs]class CorrStats(AttribDict):
"""
From Obspy, but with the difference that some items can be lists and that
corr_start and corr_end are introduced, some other differences only for
correlations.
A container for additional header information of a ObsPy Trace object.
A ``Stats`` object may contain all header information (also known as meta
data) of a :class:`~seismic.correlate.stream.CorrTrace` object. Those
headers may be accessed or modified either in the dictionary style or
directly via a corresponding attribute. There are various default
attributes which are required by every waveform import and export modules
within ObsPy such as :mod:`obspy.io.mseed`.
:type header: dict or :class:`~obspy.core.trace.Stats`, optional
:param header: Dictionary containing meta information of a single
:class:`~obspy.core.trace.Trace` object. Possible keywords are
summarized in the following `Default Attributes`_ section.
.. rubric:: Basic Usage
>>> stats = CorrStats()
>>> stats.network = 'BW'
>>> print(stats['network'])
BW
>>> stats['station'] = 'MANZ'
>>> print(stats.station)
MANZ
.. rubric:: _`Default Attributes`
``sampling_rate`` : float, optional
Sampling rate in hertz (default value is 1.0).
``delta`` : float, optional
Sample distance in seconds (default value is 1.0).
``calib`` : float, optional
Calibration factor (default value is 1.0).
``npts`` : int, optional
Number of sample points (default value is 0, which implies that no data
is present).
``network`` : string, optional
Network code (default is an empty string).
``location`` : string, optional
Location code (default is an empty string).
``station`` : string, optional
Station code (default is an empty string).
``channel`` : string, optional
Channel code (default is an empty string).
``starttime`` : :class:`~obspy.core.utcdatetime.UTCDateTime`, optional
Date and time of the first data sample used for correlation in UTC
(default value is "1970-01-01T00:00:00.0Z").
``endtime`` : :class:`~obspy.core.utcdatetime.UTCDateTime`, optional
Date and time of the last data sample used for correlation in UTC
(default value is "1970-01-01T00:00:00.0Z").
``corr_start``: :class:`~obspy.core.utcdatetime.UTCDateTime`, optional
Date and time of the first data sample used for correlation in UTC
(default value is "1970-01-01T00:00:00.0Z").
``corr_end``: :class:`~obspy.core.utcdatetime.UTCDateTime`, optional
Date and time of the last data sample used for correlation in UTC
(default value is "1970-01-01T00:00:00.0Z").
``start_lag``: float, optional
Lag of the first sample in seconds (usually negative)
``end_lag``: float, optional
Lag of the last sample in seconds.
.. rubric:: Notes
(1) The attributes ``sampling_rate`` and ``delta`` are linked to each
other. If one of the attributes is modified the other will be
recalculated.
>>> stats = Stats()
>>> stats.sampling_rate
1.0
>>> stats.delta = 0.005
>>> stats.sampling_rate
200.0
(2) The attributes ``start_lag``, ``npts``, ``sampling_rate`` and ``delta``
are monitored and used to automatically calculate the ``end_lag``.
>>> stats = Stats()
>>> stats.npts = 61
>>> stats.delta = 1.0
>>> stats.start_lag = -30
>>> stats.end_lag
30
>>> stats.delta = 0.5
>>> stats.end_lag
0
(3) The attribute ``endtime``, ``end_lag``, and ``starttime`` are
read only and cannot be modified. ``starttime`` and ``endtime`` are
just simply aliases of ``corr_start`` and ``corr_end``.
>>> stats = Stats()
>>> stats.endtime = UTCDateTime(2009, 1, 1, 12, 0, 0)
Traceback (most recent call last):
...
AttributeError: Attribute "endtime" in Stats object is read only!
>>> stats['endtime'] = UTCDateTime(2009, 1, 1, 12, 0, 0)
Traceback (most recent call last):
...
AttributeError: Attribute "endtime" in Stats object is read only!
(4)
The attribute ``npts`` will be automatically updated from the
:class:`~seismic.correlate.stream.CorrTrace` object.
>>> trace = CorrTrace()
>>> trace.stats.npts
0
>>> trace.data = np.array([1, 2, 3, 4])
>>> trace.stats.npts
4
(5)
The attribute ``component`` can be used to get or set the component,
i.e. the last character of the ``channel`` attribute.
>>> stats = Stats()
>>> stats.channel = 'HHZ'
>>> stats.component # doctest: +SKIP
'Z'
>>> stats.component = 'L'
>>> stats.channel # doctest: +SKIP
'HHL'
"""
# set of read only attrs
readonly = ['endtime', 'end_lag', 'starttime']
# default values
defaults = {
'sampling_rate': 1.0,
'delta': 1.0,
'starttime': UTCDateTime(0),
'endtime': UTCDateTime(0),
'corr_start': UTCDateTime(0),
'corr_end': UTCDateTime(0),
'start_lag': 0,
'end_lag': 0,
'npts': 0,
'calib': 1.0,
'network': '',
'station': '',
'location': '',
'channel': '',
}
# keys which need to refresh derived values
_refresh_keys = {
'delta', 'sampling_rate', 'corr_start', 'corr_end', 'npts',
'start_lag'}
# dict of required types for certain attrs
_types = {
'network': (str),
'station': (str),
}
def __init__(self, header={}):
"""
"""
super(CorrStats, self).__init__(header)
def __setitem__(self, key, value):
"""
"""
if key in self._refresh_keys:
# ensure correct data type
if key == 'delta':
key = 'sampling_rate'
try:
value = 1.0 / float(value)
except ZeroDivisionError:
value = 0.0
elif key == 'sampling_rate':
value = float(value)
elif key == 'start_lag':
value = float(value)
elif key == 'npts':
if not isinstance(value, int):
value = int(value)
# set current key
super(CorrStats, self).__setitem__(key, value)
# set derived value: delta
try:
delta = 1.0 / float(self.sampling_rate)
except ZeroDivisionError:
delta = 0
self.__dict__['delta'] = delta
# set derived value: endtime
if self.npts == 0:
timediff = 0
else:
timediff = float(self.npts - 1) * delta
self.__dict__['end_lag'] = self.start_lag + timediff
self.__dict__['endtime'] = self.corr_end
self.__dict__['starttime'] = self.corr_start
return
if key == 'component':
key = 'channel'
value = str(value)
if len(value) != 3:
msg = 'Component must be set with three characters, e.g. E-Z'
raise ValueError(msg)
a, b = self.channel.split('-')
an, bn = value.split('-')
value = a[:-1] + an + '-' + b[:-1] + bn
# all other keys
if isinstance(value, dict):
super(CorrStats, self).__setitem__(key, AttribDict(value))
else:
super(CorrStats, self).__setitem__(key, value)
__setattr__ = __setitem__
def __getitem__(self, key, default=None):
"""
"""
if key == 'component':
ch = super(CorrStats, self).__getitem__('channel', default)
a, b = ch.split('-')
return a[-1]+'-'+b[-1]
elif key == 'id':
net = super(CorrStats, self).__getitem__('network', default)
sta = super(CorrStats, self).__getitem__('station', default)
ch = super(CorrStats, self).__getitem__('channel', default)
return f'{net}.{sta}.{ch}'
else:
return super(CorrStats, self).__getitem__(key, default)
def __str__(self):
"""
Return better readable string representation of Stats object.
"""
priorized_keys = ['network', 'station', 'location', 'channel',
'corr_start', 'corr_end', 'start_lag', 'end_lag',
'sampling_rate', 'delta', 'npts', 'calib']
return self._pretty_str(priorized_keys)
def _repr_pretty_(self, p, cycle):
p.text(str(self))