Source code for seismic.inventory.response

"""
Description:
    Implements a Class for reading/storing responses from a number of sources.
    The ResponseFactory class is used for attaching 'bogus' responses to
    station inventories that lack them.

References:

CreationDate:   14/02/19

Developer:      rakib.hassan@ga.gov.au

Revision History:
    LastUpdate:     14/02/19   RH
    LastUpdate:     dd/mm/yyyy  Who     Optional description
"""

from collections import defaultdict
from io import StringIO

from obspy.core import UTCDateTime
from obspy import read_inventory


[docs]class ResponseFactory: """ The ResponseFactory class encapsulates the generation of a collection of named Instrument Response Objects from a variety of sources. Currently it provides the facility to create Response objects from two sources, namely, Poles and Zeroes supplied by the user and from StationXML files generated from RESP files using the PDCC tool (link below). The conversion of RESP files into a corresponding StationXML file, at this stage, must take place externally, because ObsPy lacks that functionality. The intended usage of this class during the creation of an ASDF dataset is as follows:: 1. User creates a number of uniquely named Response objects (see associated tests as well) pertaining to different channels in a given survey. 2. User fetches these Response objects from an instance of ResponseFactory as needed, while creating ObsPy Channel objects, during which Response objects can be passed in as an argument. 3. User builds a hierarchy of channel->station->network inventories, with the appropriate instrument response information embedded 4. The master FDSN StaionXML file output after step 3 can then be converted into an SC3ML file (which can be ingested by SeisComp3) using the fdsnxml2inv tool. PDCC tool: https://ds.iris.edu/ds/nodes/dmc/software/downloads/pdcc/ """ def __init__(self): self.m_responseInventory = defaultdict(list) # end func
[docs] class ResponseFromInventory(object): """Helper class to get Obspy Response object from an Inventory :raises RuntimeError: Raises error if response not found """ def __init__(self, source_inventory): """Constructor :param source_inventory: Inventory from which to extract response :type source_inventory: obspy.core.inventory.inventory.Inventory """ self.m_inventory = source_inventory self._get_response_from_inventory() #end func def _get_response_from_inventory(self): n = None s = None c = None found = 0 # Extract network, station and channel codes if self.m_inventory.networks: n = self.m_inventory.networks[0] found += 1 if n.stations: s = n.stations[0] found += 1 if n.stations[0].channels: c = n.stations[0].channels[0] found += 1 # end if # end if # end if if (found < 3): msg = 'Network, station or channel information missing in RESP file.' raise RuntimeError(msg) # end if seed_id = '%s.%s..%s' % (n.code, s.code, c.code) self.m_response = self.m_inventory.get_response(seed_id, s.start_date)
#end func # end class
[docs] class ResponseFromStationXML(ResponseFromInventory): """Helper class to get Obspy Response object from a station xml file """ def __init__(self, respFileName): """Constructor :param respFileName: XML file to load :type respFileName: str """ xml_inventory = read_inventory(respFileName) super(ResponseFactory.ResponseFromStationXML, self).__init__(xml_inventory)
# end class
[docs] class ResponseFromPAZ: def __init__(self, pzTransferFunctionType='LAPLACE (RADIANS/SECOND)', normFactor=8e4, normFreq=1e-2, stageGain=2e3, stageGainFreq=1e-2, poles=[0 + 0j], zeros=[0 + 0j]): self.m_base = u'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <FDSNStationXML xmlns="http://www.fdsn.org/xml/station/1" schemaVersion="1"> <Source>-i</Source> <Module>fdsn-stationxml-converter/1.0.9</Module> <ModuleURI>http://www.iris.edu/fdsnstationconverter</ModuleURI> <Created>2017-09-13T16:06:47.256+10:00</Created> <Network code="IU"> <Description></Description> <Station code="ANMO" startDate="2002-11-19T21:07:00.000" endDate="2008-06-30T00:00:00.000"> <Latitude>0.0</Latitude> <Longitude>0.0</Longitude> <Elevation>0.0</Elevation> <Site> <Name>0</Name> </Site> <CreationDate>2002-11-19T21:07:00.000</CreationDate> <Channel locationCode="00" code="BHZ" startDate="2002-11-19T21:07:00.000" endDate="2008-06-30T00:00:00.000"> <Latitude>0.0</Latitude> <Longitude>0.0</Longitude> <Elevation>0.0</Elevation> <Depth>0.0</Depth> <Azimuth>0.0</Azimuth> <Dip>0.0</Dip> <SampleRate>0.0</SampleRate> <ClockDrift>0.0</ClockDrift> <Response> <InstrumentSensitivity> <Value>8.11597E8</Value> <Frequency>0.02</Frequency> <InputUnits> <Name>M/S</Name> <Description>Velocity in Meters Per Second</Description> </InputUnits> <OutputUnits> <Name>COUNTS</Name> <Description>Digital Counts</Description> </OutputUnits> </InstrumentSensitivity> <Stage number="1"> <PolesZeros> <InputUnits> <Name>M/S</Name> <Description>Velocity in Meters Per Second</Description> </InputUnits> <OutputUnits> <Name>V</Name> <Description>Volts</Description> </OutputUnits> <PzTransferFunctionType>LAPLACE (RADIANS/SECOND)</PzTransferFunctionType> <NormalizationFactor>86083.0</NormalizationFactor> <NormalizationFrequency>0.02</NormalizationFrequency> <Zero number="0"> <Real plusError="0.0" minusError="0.0">0.0</Real> <Imaginary plusError="0.0" minusError="0.0">0.0</Imaginary> </Zero> <Zero number="1"> <Real plusError="0.0" minusError="0.0">0.0</Real> <Imaginary plusError="0.0" minusError="0.0">0.0</Imaginary> </Zero> <Pole number="0"> <Real plusError="0.0" minusError="0.0">-59.4313</Real> <Imaginary plusError="0.0" minusError="0.0">0.0</Imaginary> </Pole> <Pole number="1"> <Real plusError="0.0" minusError="0.0">-22.7121</Real> <Imaginary plusError="0.0" minusError="0.0">27.1065</Imaginary> </Pole> <Pole number="2"> <Real plusError="0.0" minusError="0.0">-22.7121</Real> <Imaginary plusError="0.0" minusError="0.0">-27.1065</Imaginary> </Pole> <Pole number="3"> <Real plusError="0.0" minusError="0.0">-0.0048004</Real> <Imaginary plusError="0.0" minusError="0.0">0.0</Imaginary> </Pole> <Pole number="4"> <Real plusError="0.0" minusError="0.0">-0.073199</Real> <Imaginary plusError="0.0" minusError="0.0">0.0</Imaginary> </Pole> </PolesZeros> <StageGain> <Value>1935.0</Value> <Frequency>0.02</Frequency> </StageGain> </Stage> </Response> </Channel> </Station> </Network> </FDSNStationXML> ''' self.m_response = None self.m_pzTransferFunctionType = pzTransferFunctionType self.m_normFactor = normFactor self.m_normFreq = normFreq self.m_stageGain = stageGain self.m_stageGainFreq = stageGainFreq self.m_poles = poles self.m_zeros = zeros # Generate a valid response inventory based on the fdsnstationxml file, which # was downloaded from IRIS as an example. inv = read_inventory(StringIO(self.m_base)) datetime = UTCDateTime("2002-11-19T21:07:00.000") # Fetch the response object and adapt its parameters based on user-input. self.m_response = inv.get_response('IU.ANMO.00.BHZ', datetime) self.m_response.instrument_sensitivity = None # Get rid of redundant information self.m_response.response_stages[0].pz_transfer_function_type = self.m_pzTransferFunctionType self.m_response.response_stages[0].normalization_factor = self.m_normFactor self.m_response.response_stages[0].normalization_frequency = self.m_normFreq self.m_response.response_stages[0].stage_gain = self.m_stageGain self.m_response.response_stages[0].stage_gain_frequency = self.m_stageGainFreq self.m_response.response_stages[0].poles = poles self.m_response.response_stages[0].zeros = zeros
# end func # end class
[docs] def CreateFromInventory(self, name, obspy_inventory): """Create response from an Inventory :param name: Name of the response for later retrieval :type name: str :param obspy_inventory: Inventory from which to extract response :type obspy_inventory: obspy.core.inventory.inventory.Inventory """ self.m_responseInventory[name] = ResponseFactory.ResponseFromInventory(obspy_inventory)
# end func
[docs] def CreateFromStationXML(self, name, respFileName): """Create response from an XML file :param name: Name of the response for later retrieval :type name: str :param respFileName: XML file to load :type respFileName: str """ self.m_responseInventory[name] = ResponseFactory.ResponseFromStationXML(respFileName)
# end func
[docs] def CreateFromPAZ(self, name, pzTransferFunctionType, normFactor, normFreq, stageGain, stageGainFreq, poles, zeros): self.m_responseInventory[name] = ResponseFactory.ResponseFromPAZ(pzTransferFunctionType, normFactor, normFreq, stageGain, stageGainFreq, poles, zeros)
#end func
[docs] def getResponse(self, name): """Retrieve response by name :param name: Name given to response at creation time :type name: str :raises RuntimeError: Raises error if name is not recognized :return: The requested response :rtype: obspy.core.inventory.response.Response """ if (name in self.m_responseInventory.keys()): return self.m_responseInventory[name].m_response else: msg = "Response with name: %s not found.." % (name) raise RuntimeError(msg)
# end if # end func # end class