Source code for

#!/usr/bin/env python
# -*- coding: utf-8 -*-
Provides the Channel class.

    Lion Krischer (, 2013
    GNU Lesser General Public License, Version 3
import warnings

from obspy.core.util.decorator import deprecated_keywords
from obspy.core.util.deprecation_helpers import ObsPyDeprecationWarning
from obspy.core.util.obspy_types import (
    FloatWithUncertainties, FloatWithUncertaintiesAndUnit)
from . import BaseNode
from .util import (Azimuth, ClockDrift, Dip, Distance, Latitude, Longitude,

[docs]class Channel(BaseNode): """ From the StationXML definition: Equivalent to SEED blockette 52 and parent element for the related response blockettes. """
[docs] @deprecated_keywords({"equipment": "equipments"}) def __init__(self, code, location_code, latitude, longitude, elevation, depth, azimuth=None, dip=None, types=None, external_references=None, sample_rate=None, sample_rate_ratio_number_samples=None, sample_rate_ratio_number_seconds=None, storage_format=None, clock_drift_in_seconds_per_sample=None, calibration_units=None, calibration_units_description=None, sensor=None, pre_amplifier=None, data_logger=None, equipments=None, response=None, description=None, comments=None, start_date=None, end_date=None, restricted_status=None, alternate_code=None, historical_code=None, data_availability=None, identifiers=None, water_level=None, source_id=None): """ :type code: str :param code: The SEED channel code for this channel :type location_code: str :param location_code: The SEED location code for this channel :type latitude: :class:`~obspy.core.inventory.util.Latitude` :param latitude: Latitude coordinate of this channel's sensor. :type longitude: :class:`~obspy.core.inventory.util.Longitude` :param longitude: Longitude coordinate of this channel's sensor. :type elevation: float :param elevation: Elevation of the sensor. :type depth: float :param depth: The local depth or overburden of the instrument's location. For downhole instruments, the depth of the instrument under the surface ground level. For underground vaults, the distance from the instrument to the local ground level above. :type azimuth: float :param azimuth: Azimuth of the sensor in degrees from North, clockwise. :type dip: float :param dip: Dip of the instrument in degrees, down from horizontal. :type types: list[str] :param types: The type of data this channel collects. Corresponds to channel flags in SEED blockette 52. The SEED volume producer could use the first letter of an Output value as the SEED channel flag. Possible values: TRIGGERED, CONTINUOUS, HEALTH, GEOPHYSICAL, WEATHER, FLAG, SYNTHESIZED, INPUT, EXPERIMENTAL, MAINTENANCE, BEAM :type external_references: list of :class:`~obspy.core.inventory.util.ExternalReference` :param external_references: URI of any type of external report, such as data quality reports. :type sample_rate: float :param sample_rate: This is a group of elements that represent sample rate. If this group is included, then SampleRate, which is the sample rate in samples per second, is required. SampleRateRatio, which is expressed as a ratio of number of samples in a number of seconds, is optional. If both are included, SampleRate should be considered more definitive. :type sample_rate_ratio_number_samples: int :param sample_rate_ratio_number_samples: The sample rate expressed as number of samples in a number of seconds. This is the number of samples. :type sample_rate_ratio_number_seconds: int :param sample_rate_ratio_number_seconds: The sample rate expressed as number of samples in a number of seconds. This is the number of seconds. :param storage_format: Ignored. Removed with ObsPy version 1.2.0 in accordance with StationXML 1.1 :type clock_drift_in_seconds_per_sample: float :param clock_drift_in_seconds_per_sample: A tolerance value, measured in seconds per sample, used as a threshold for time error detection in data from the channel. :type calibration_units: str :param calibration_units: Name of units , e.g. "M/S", "M", ... :type calibration_units_description: str :param calibration_units_description: Description of units, e.g. "Velocity in meters per second", ... :type sensor: :class:`~obspy.core.inventory.util.Equipment` :param sensor: The sensor :type pre_amplifier: :class:`~obspy.core.inventory.util.Equipment` :param pre_amplifier: The pre-amplifier :type data_logger: :class:`~obspy.core.inventory.util.Equipment` :param data_logger: The data-logger :type equipments: list of :class:`~obspy.core.inventory.util.Equipment` :param equipments: Other station equipment :type response: :class:`~obspy.core.inventory.response.Response` :param response: The response of the channel :type description: str :param description: A description of the resource :type comments: list of :class:`~obspy.core.inventory.util.Comment` :param comments: An arbitrary number of comments to the resource :type start_date: :class:`~obspy.core.utcdatetime.UTCDateTime` :param start_date: The start date of the resource :type end_date: :class:`~obspy.core.utcdatetime.UTCDateTime` :param end_date: The end date of the resource :type restricted_status: str :param restricted_status: The restriction status :type alternate_code: str :param alternate_code: A code used for display or association, alternate to the SEED-compliant code. :type historical_code: str :param historical_code: A previously used code if different from the current code. :type data_availability: :class:`~obspy.core.inventory.util.DataAvailability` :param data_availability: Information about time series availability for the channel. :type identifiers: list[str], optional :param identifiers: Persistent identifiers for network/station/channel (schema version >=1.1). URIs are in general composed of a 'scheme' and a 'path' (optionally with additional components), the two of which separated by a colon. :type water_level: float, optional :param water_level: Elevation of the water surface in meters for underwater sites, where 0 is sea level. (schema version >=1.1) :type source_id: str, optional :param source_id: A data source identifier in URI form (schema version >=1.1). URIs are in general composed of a 'scheme' and a 'path' (optionally with additional components), the two of which separated by a colon. """ self.location_code = location_code self.latitude = latitude self.longitude = longitude self.elevation = elevation self.depth = depth self.azimuth = azimuth self.dip = dip self.water_level = water_level self.types = types or [] self.external_references = external_references or [] self.sample_rate = sample_rate self.sample_rate_ratio_number_samples = \ sample_rate_ratio_number_samples self.sample_rate_ratio_number_seconds = \ sample_rate_ratio_number_seconds if storage_format is not None: msg = ("Attribute 'storage_format' was removed in accordance with " "StationXML 1.1, ignoring.") warnings.warn(msg, ObsPyDeprecationWarning) self.clock_drift_in_seconds_per_sample = \ clock_drift_in_seconds_per_sample self.calibration_units = calibration_units self.calibration_units_description = calibration_units_description self.sensor = sensor self.pre_amplifier = pre_amplifier self.data_logger = data_logger if isinstance(equipments, Equipment): equipments = [equipments] self.equipments = equipments or [] self.response = response super(Channel, self).__init__( code=code, description=description, comments=comments, start_date=start_date, end_date=end_date, restricted_status=restricted_status, alternate_code=alternate_code, historical_code=historical_code, data_availability=data_availability, identifiers=identifiers, source_id=source_id)
@property def storage_format(self): msg = ("Attribute 'storage_format' was removed in accordance with " "StationXML 1.1, returning None.") warnings.warn(msg, ObsPyDeprecationWarning) return None @storage_format.setter def storage_format(self, value): msg = ("Attribute 'storage_format' was removed in accordance with " "StationXML 1.1, ignoring.") warnings.warn(msg, ObsPyDeprecationWarning) @property def equipment(self): msg = ("Attribute 'equipment' (holding a single Equipment) is " "deprecated in favor of 'equipments' which now holds a list " "of Equipment objects (following changes in StationXML 1.1) " "and might be removed in the future. Returning the first entry " "found in 'equipments'.") warnings.warn(msg, ObsPyDeprecationWarning) equipments = self.equipments if not equipments: return None return equipments[0] @equipment.setter def equipment(self, value): msg = ("Attribute 'equipment' (holding a single Equipment) is " "deprecated in favor of 'equipments' which now holds a list " "of Equipment objects (following changes in StationXML 1.1) " "and might be removed in the future. Setting 'equipments' " "attribute with a list with the given item as only entry.") warnings.warn(msg, ObsPyDeprecationWarning) self.equipments = [value]
[docs] def __str__(self): ret = ( "Channel '{id}', Location '{location}' {description}\n" "{availability}" "\tTime range: {start_date} - {end_date}\n" "\tLatitude: {latitude:.4f}, Longitude: {longitude:.4f}, " "Elevation: {elevation:.1f} m, Local Depth: {depth:.1f} m\n" "{azimuth}" "{dip}" "{channel_types}" "{sampling_rate}" "{sensor}" "{response}")\ .format( id=self.code, location=self.location_code, latitude=self.latitude, longitude=self.longitude, elevation=self.elevation, depth=self.depth, availability=("\t%s\n" % str(self.data_availability) if self.data_availability else ""), description=("(%s)" % self.description if self.description else ""), start_date=str(self.start_date) if self.start_date else "--", end_date=str(self.end_date) if self.end_date else "--", azimuth=("\tAzimuth: %.2f degrees from north, clockwise\n" % self.azimuth if self.azimuth is not None else ""), dip=("\tDip: %.2f degrees down from horizontal\n" % self.dip if self.dip is not None else ""), channel_types=("\tChannel types: %s\n" % ", ".join(self.types) if self.types else ""), sampling_rate=("\tSampling Rate: %.2f Hz\n" % self.sample_rate if self.sample_rate else ""), sensor=("\tSensor (Description): %s (%s)\n" % ( self.sensor.type, self.sensor.description) if self.sensor else ""), response=("\tResponse information available" if self.response else "")) return ret
[docs] def _repr_pretty_(self, p, cycle): p.text(str(self))
@property def location_code(self): return self._location_code @location_code.setter def location_code(self, value): self._location_code = value.strip() @property def longitude(self): return self._longitude @longitude.setter def longitude(self, value): if isinstance(value, Longitude): self._longitude = value else: self._longitude = Longitude(value) @property def latitude(self): return self._latitude @latitude.setter def latitude(self, value): if isinstance(value, Latitude): self._latitude = value else: self._latitude = Latitude(value) @property def elevation(self): return self._elevation @elevation.setter def elevation(self, value): if isinstance(value, Distance): self._elevation = value else: self._elevation = Distance(value) @property def depth(self): return self._depth @depth.setter def depth(self, value): if isinstance(value, Distance): self._depth = value else: self._depth = Distance(value) @property def azimuth(self): return self._azimuth @azimuth.setter def azimuth(self, value): if value is None: self._azimuth = None elif isinstance(value, Azimuth): self._azimuth = value else: self._azimuth = Azimuth(value) @property def dip(self): return self._dip @dip.setter def dip(self, value): if value is None: self._dip = None elif isinstance(value, Dip): self._dip = value else: self._dip = Dip(value) @property def water_level(self): return self._water_level @water_level.setter def water_level(self, value): if value is None: self._water_level = None elif isinstance(value, FloatWithUncertaintiesAndUnit): self._water_level = value else: self._water_level = FloatWithUncertaintiesAndUnit(value) @property def sample_rate(self): return self._sample_rate @sample_rate.setter def sample_rate(self, value): if value is None: self._sample_rate = None elif isinstance(value, FloatWithUncertainties): self._sample_rate = value else: self._sample_rate = FloatWithUncertainties(value) @property def clock_drift_in_seconds_per_sample(self): return self._clock_drift_in_seconds_per_sample @clock_drift_in_seconds_per_sample.setter def clock_drift_in_seconds_per_sample(self, value): if value is None: self._clock_drift_in_seconds_per_sample = None elif isinstance(value, ClockDrift): self._clock_drift_in_seconds_per_sample = value else: self._clock_drift_in_seconds_per_sample = ClockDrift(value) @property def equipments(self): return self._equipments @equipments.setter def equipments(self, value): if not hasattr(value, "__iter__"): msg = "equipments needs to be an iterable, e.g. a list." raise ValueError(msg) # make sure to unwind actual iterators, or the just might get exhausted # at some point equipments = [equipment for equipment in value] if any([not isinstance(x, Equipment) for x in equipments]): msg = "equipments can only contain Equipment objects." raise ValueError(msg) self._equipments = equipments
[docs] def plot(self, min_freq, output="VEL", start_stage=None, end_stage=None, label=None, axes=None, unwrap_phase=False, plot_degrees=False, show=True, outfile=None): """ Show bode plot of the channel's instrument response. :type min_freq: float :param min_freq: Lowest frequency to plot. :type output: str :param output: Output units. One of: ``"DISP"`` displacement, output unit is meters ``"VEL"`` velocity, output unit is meters/second ``"ACC"`` acceleration, output unit is meters/second**2 ``"DEF"`` default units, the response is calculated in output units/input units (last stage/first stage). Useful if the units for a particular type of sensor (e.g., a pressure sensor) cannot be converted to displacement, velocity or acceleration. :type start_stage: int :param start_stage: Sequence number of first stage that will be used (disregarding all earlier stages). :type end_stage: int :param end_stage: Sequence number of last stage that will be used (disregarding all later stages). :type label: str :param label: Label string for legend. :type axes: list[:class:`matplotlib.axes.Axes`, :class:`matplotlib.axes.Axes`] :param axes: List/tuple of two axes instances on which to plot the amplitude/phase spectrum. If not specified, a new figure is opened. :type unwrap_phase: bool :param unwrap_phase: Set optional phase unwrapping using NumPy. :type plot_degrees: bool :param plot_degrees: if ``True`` plot bode in degrees :type show: bool :param show: Whether to show the figure after plotting or not. Can be used to do further customization of the plot before showing it. :type outfile: str :param outfile: Output file path to directly save the resulting image (e.g. ``"/tmp/image.png"``). Overrides the ``show`` option; image will not be displayed interactively. The given path/file name is also used to automatically determine the output format. Supported file formats depend on your matplotlib backend. Most backends support png, pdf, ps, eps and svg. Defaults to ``None``. .. rubric:: Basic Usage >>> from obspy import read_inventory >>> cha = read_inventory()[0][0][0] >>> cha.plot(0.001, output="VEL") # doctest: +SKIP .. plot:: from obspy import read_inventory cha = read_inventory()[0][0][0] cha.plot(0.001, output="VEL") """ return self.response.plot( min_freq=min_freq, output=output, start_stage=start_stage, end_stage=end_stage, label=label, axes=axes, sampling_rate=self.sample_rate, unwrap_phase=unwrap_phase, plot_degrees=plot_degrees, show=show, outfile=outfile)
if __name__ == '__main__': import doctest doctest.testmod(exclude_empty=True)