def _read_station(instrumentation_register, sta_element, _ns): """ Reads the station structure :param instrumentation_register: register of instrumentation metadata :param sta_element: station element to be read :param _ns: name space """ # Read location tags longitude = _read_floattype(sta_element, _ns("longitude"), Longitude, datum=True) latitude = _read_floattype(sta_element, _ns("latitude"), Latitude, datum=True) elevation = _read_floattype(sta_element, _ns("elevation"), Distance, unit=True) station = obspy.core.inventory.Station(code=sta_element.get("code"), latitude=latitude, longitude=longitude, elevation=elevation) station.site = _read_site(sta_element, _ns) # There is no relevant info in the base node # Read the start and end date (creation, termination) from tags # "Vault" and "Geology" are not defined in sc3ml ? station.start_date = _tag2obj(sta_element, _ns("start"), obspy.UTCDateTime) station.end_date = _tag2obj(sta_element, _ns("end"), obspy.UTCDateTime) station.creation_date = _tag2obj(sta_element, _ns("start"), obspy.UTCDateTime) station.termination_date = _tag2obj(sta_element, _ns("end"), obspy.UTCDateTime) # get the restricted_status (boolean) # true is evaluated to 'open'; false to 'closed' station.restricted_status = _get_restricted_status(sta_element, _ns) # Get all the channels, sc3ml keeps these in <sensorLocation> tags in the # station element. Individual channels are contained within <stream> tags channels = [] for sen_loc_element in sta_element.findall(_ns("sensorLocation")): for channel in sen_loc_element.findall(_ns("stream")): channels.append( _read_channel(instrumentation_register, channel, _ns)) station.channels = channels return station
def _read_station(inventory_root, sta_element, _ns): """ Reads the station structure :param inventory_root: base inventory element of sc3ml :param sta_element: station element to be read :param _ns: name space """ # Read location tags longitude = _read_floattype(sta_element, _ns("longitude"), Longitude, datum=True) latitude = _read_floattype(sta_element, _ns("latitude"), Latitude, datum=True) elevation = _read_floattype(sta_element, _ns("elevation"), Distance, unit=True) station = obspy.core.inventory.Station(code=sta_element.get("code"), latitude=latitude, longitude=longitude, elevation=elevation) station.site = _read_site(sta_element, _ns) # There is no relevant info in the base node # Read the start and end date (creation, termination) from tags # "Vault" and "Geology" are not defined in sc3ml ? station.start_date = _tag2obj(sta_element, _ns("start"), obspy.UTCDateTime) station.end_date = _tag2obj(sta_element, _ns("end"), obspy.UTCDateTime) station.creation_date = _tag2obj(sta_element, _ns("start"), obspy.UTCDateTime) station.termination_date = _tag2obj(sta_element, _ns("end"), obspy.UTCDateTime) # get the restricted_status (boolean) # true is evaluated to 'open'; false to 'closed' station.restricted_status = _get_restricted_status(sta_element, _ns) # Get all the channels, sc3ml keeps these in <sensorLocation> tags in the # station element. Individual channels are contained within <stream> tags channels = [] for sen_loc_element in sta_element.findall(_ns("sensorLocation")): for channel in sen_loc_element.findall(_ns("stream")): channels.append(_read_channel(inventory_root, channel, _ns)) station.channels = channels return station
def _read_response_stage(stage, _ns, rate, stage_number, input_units, output_units): elem_type = stage.tag.split("}")[1] stage_sequence_number = stage_number # Obtain the stage gain and frequency # Default to a gain of 0 and frequency of 0 if missing stage_gain = _tag2obj(stage, _ns("gain"), float) or 0 stage_gain_frequency = _tag2obj(stage, _ns("gainFrequency"), float) or float(0.00) name = stage.get("name") if name is not None: name = str(name) resource_id = stage.get("publicID") if resource_id is not None: resource_id = str(resource_id) # Determine the decimation parameters # This is dependent on the type of stage # Decimation delay/correction need to be normalized if elem_type == "responseFIR": decimation_factor = _tag2obj(stage, _ns("decimationFactor"), int) if rate != 0.0: temp = _tag2obj(stage, _ns("delay"), float) / rate decimation_delay = _read_float_var(temp, FloatWithUncertaintiesAndUnit, unit=True) temp = _tag2obj(stage, _ns("correction"), float) / rate decimation_corr = _read_float_var(temp, FloatWithUncertaintiesAndUnit, unit=True) else: decimation_delay = _read_float_var("inf", FloatWithUncertaintiesAndUnit, unit=True) decimation_corr = _read_float_var("inf", FloatWithUncertaintiesAndUnit, unit=True) decimation_input_sample_rate = _read_float_var(rate, Frequency) decimation_offset = int(0) elif elem_type == "datalogger": decimation_factor = int(1) decimation_delay = _read_float_var(0.00, FloatWithUncertaintiesAndUnit, unit=True) decimation_corr = _read_float_var(0.00, FloatWithUncertaintiesAndUnit, unit=True) decimation_input_sample_rate = _read_float_var(rate, Frequency) decimation_offset = int(0) elif elem_type == "responsePAZ" or elem_type == "responsePolynomial": decimation_factor = None decimation_delay = None decimation_corr = None decimation_input_sample_rate = None decimation_offset = None else: raise ValueError("Unknown type of response: " + str(elem_type)) # set up list of for this stage arguments kwargs = { "stage_sequence_number": stage_sequence_number, "input_units": str(input_units), "output_units": str(output_units), "input_units_description": None, "output_units_description": None, "resource_id": None, "resource_id2": resource_id, "stage_gain": stage_gain, "stage_gain_frequency": stage_gain_frequency, "name": name, "description": None, "decimation_input_sample_rate": decimation_input_sample_rate, "decimation_factor": decimation_factor, "decimation_offset": decimation_offset, "decimation_delay": decimation_delay, "decimation_correction": decimation_corr, } # Different processing for different types of responses # currently supported: # PAZ # COEFF # FIR # Polynomial response is not supported, could not find example if elem_type == "responsePAZ": # read normalization params normalization_freq = _read_floattype(stage, _ns("normalizationFrequency"), Frequency) normalization_factor = _tag2obj(stage, _ns("normalizationFactor"), float) # Parse the type of the transfer function # A: Laplace (rad) # B: Laplace (Hz) # D: digital (z-transform) pz_transfer_function_type = _tag2obj(stage, _ns("type"), str) if pz_transfer_function_type == "A": pz_transfer_function_type = "LAPLACE (RADIANS/SECOND)" elif pz_transfer_function_type == "B": pz_transfer_function_type = "LAPLACE (HERTZ)" elif pz_transfer_function_type == "D": pz_transfer_function_type = "DIGITAL (Z-TRANSFORM)" else: msg = ("Unknown transfer function code %s. Defaulting to Laplace" "(rad)") % pz_transfer_function_type warnings.warn(msg) pz_transfer_function_type = "LAPLACE (RADIANS/SECOND)" # Parse string of poles and zeros # paz are stored as a string in sc3ml # e.g. (-0.01234,0.01234) (-0.01234,-0.01234) zeros_array = stage.find(_ns("zeros")).text poles_array = stage.find(_ns("poles")).text if zeros_array is not None: zeros_array = zeros_array.split(" ") else: zeros_array = [] if poles_array is not None: poles_array = poles_array.split(" ") else: poles_array = [] # Keep counter for pole/zero number cnt = 0 poles = [] zeros = [] for el in poles_array: poles.append(_tag2pole_or_zero(el, cnt)) cnt += 1 for el in zeros_array: zeros.append(_tag2pole_or_zero(el, cnt)) cnt += 1 # Return the paz response return PolesZerosResponseStage( pz_transfer_function_type=pz_transfer_function_type, normalization_frequency=normalization_freq, normalization_factor=normalization_factor, zeros=zeros, poles=poles, **kwargs ) elif elem_type == "datalogger": cf_transfer_function_type = "DIGITAL" numerator = [] denominator = [] return CoefficientsTypeResponseStage( cf_transfer_function_type=cf_transfer_function_type, numerator=numerator, denominator=denominator, **kwargs ) elif elem_type == "responsePolynomial": # Polynomial response (UNTESTED) # Currently not implemented in ObsPy (20-11-2015) f_low = None f_high = None max_err = None appr_type = _tag2obj(stage, _ns("approximationType"), str) appr_low = _tag2obj(stage, _ns("approximationLowerBound"), float) appr_high = _tag2obj(stage, _ns("approximationUpperBound"), float) coeffs_str = _tag2obj(stage, _ns("coefficients"), str) if coeffs_str is not None: coeffs = coeffs_str.split(" ") coeffs_float = [] i = 0 # pass additional mapping of coefficient counter # so that a proper stationXML can be formatted for c in coeffs: temp = _read_float_var(c, FilterCoefficient, additional_mapping={str("number"): i}) coeffs_float.append(temp) i += 1 return PolynomialResponseStage( approximation_type=appr_type, frequency_lower_bound=f_low, frequency_upper_bound=f_high, approximation_lower_bound=appr_low, approximation_upper_bound=appr_high, maximum_error=max_err, coefficients=coeffs, **kwargs ) elif elem_type == "responseFIR": # For the responseFIR obtain the symmetry and # list of coefficients coeffs_str = _tag2obj(stage, _ns("coefficients"), str) coeffs_float = [] if coeffs_str is not None and coeffs_str != "None": coeffs = coeffs_str.split(" ") i = 0 # pass additional mapping of coefficient counter # so that a proper stationXML can be formatted for c in coeffs: temp = _read_float_var(c, FilterCoefficient, additional_mapping={str("number"): i}) coeffs_float.append(temp) i += 1 # Write the FIR symmetry to what ObsPy expects # A: NONE, # B: ODD, # C: EVEN symmetry = _tag2obj(stage, _ns("symmetry"), str) if symmetry == "A": symmetry = "NONE" elif symmetry == "B": symmetry = "ODD" elif symmetry == "C": symmetry = "EVEN" else: raise ValueError("Unknown symmetry metric; expected A, B, or C") return FIRResponseStage(coefficients=coeffs_float, symmetry=symmetry, **kwargs)
def _read_channel(inventory_root, cha_element, _ns): """ reads channel element from sc3ml format :param sta_element: channel element :param _ns: namespace """ code = cha_element.get("code") # Information is also kept within the parent <sensorLocation> element sen_loc_element = cha_element.getparent() location_code = sen_loc_element.get("code") # get site info from the <sensorLocation> element longitude = _read_floattype(sen_loc_element, _ns("longitude"), Longitude, datum=True) latitude = _read_floattype(sen_loc_element, _ns("latitude"), Latitude, datum=True) elevation = _read_floattype(sen_loc_element, _ns("elevation"), Distance, unit=True) depth = _read_floattype(cha_element, _ns("depth"), Distance, unit=True) channel = obspy.core.inventory.Channel( code=code, location_code=location_code, latitude=latitude, longitude=longitude, elevation=elevation, depth=depth ) # obtain the sensorID and link to particular publicID <sensor> element # in the inventory base node sensor_id = cha_element.get("sensor") sensor_element = inventory_root.find(_ns("sensor[@publicID='" + sensor_id + "']")) # obtain the poles and zeros responseID and link to particular # <responsePAZ> publicID element in the inventory base node if sensor_element is not None: response_id = sensor_element.get("response") if response_id is not None: resp_type = response_id.split("#")[0] if resp_type == "ResponsePAZ": search = "responsePAZ[@publicID='" + response_id + "']" response_element = inventory_root.find(_ns(search)) elif resp_type == "ResponsePolynomial": search = "responsePolynomial[@publicID='" + response_id + "']" response_element = inventory_root.find(_ns(search)) else: response_element = None else: response_element = None # obtain the dataloggerID and link to particular <responsePAZ> publicID # element in the inventory base node datalogger_id = cha_element.get("datalogger") search = "datalogger[@publicID='" + datalogger_id + "']" data_log_element = inventory_root.find(_ns(search)) channel.restricted_status = _get_restricted_status(cha_element, _ns) # There is no further information in the attributes of <stream> # Start and end date are included as tags instead channel.start_date = _tag2obj(cha_element, _ns("start"), obspy.UTCDateTime) channel.end_date = _tag2obj(cha_element, _ns("end"), obspy.UTCDateTime) # Determine sample rate (given is a numerator, denominator) # Assuming numerator is # samples and denominator is # seconds numerator = _tag2obj(cha_element, _ns("sampleRateNumerator"), int) denominator = _tag2obj(cha_element, _ns("sampleRateDenominator"), int) rate = numerator / denominator channel.sample_rate_ratio_number_samples = numerator channel.sample_rate_ratio_number_seconds = denominator channel.sample_rate = _read_float_var(rate, SampleRate) if sensor_element is not None: channel.sensor = _read_sensor(sensor_element, _ns) if data_log_element is not None: channel.data_logger = _read_datalogger(data_log_element, _ns) temp = _read_floattype(data_log_element, _ns("maxClockDrift"), ClockDrift) if channel.sample_rate != 0.0: channel.clock_drift_in_seconds_per_sample = _read_float_var(temp / channel.sample_rate, ClockDrift) else: msg = "Clock drift division by sample rate of 0: using sec/sample" warnings.warn(msg) channel.sample_rate = temp channel.azimuth = _read_floattype(cha_element, _ns("azimuth"), Azimuth) channel.dip = _read_floattype(cha_element, _ns("dip"), Dip) channel.storage_format = _tag2obj(cha_element, _ns("format"), str) if channel.sample_rate == 0.0: msg = "Something went hopelessly wrong, found sampling-rate of 0!" warnings.warn(msg) # Begin to collect digital/analogue filter chains # This information is stored as an array in the datalogger element response_fir_id = [] response_paz_id = [] if data_log_element is not None: # Find the decimation element with a particular num/denom decim_element = data_log_element.find( _ns( "decimation[@sampleRateDenominator='" + str(int(denominator)) + "'][@sampleRateNumerator='" + str(int(numerator)) + "']" ) ) analogue_filter_chain = _tag2obj(decim_element, _ns("analogueFilterChain"), str) if analogue_filter_chain is not None: response_paz_id = analogue_filter_chain.split(" ") digital_filter_chain = _tag2obj(decim_element, _ns("digitalFilterChain"), str) if digital_filter_chain is not None: response_fir_id = digital_filter_chain.split(" ") channel.response = _read_response( inventory_root, sensor_element, response_element, cha_element, data_log_element, _ns, channel.sample_rate, response_fir_id, response_paz_id, ) return channel
def _read_response_stage(stage, _ns, rate, stage_number, input_units, output_units): elem_type = stage.tag.split("}")[1] stage_sequence_number = stage_number # Obtain the stage gain and frequency # Default to a gain of 0 and frequency of 0 if missing stage_gain = _tag2obj(stage, _ns("gain"), float) or 0 stage_gain_frequency = _tag2obj(stage, _ns("gainFrequency"), float) or float(0.00) name = stage.get("name") if name is not None: name = str(name) resource_id = stage.get("publicID") if resource_id is not None: resource_id = str(resource_id) # Determine the decimation parameters # This is dependent on the type of stage # Decimation delay/correction need to be normalized if(elem_type == "responseFIR"): decimation_factor = _tag2obj(stage, _ns("decimationFactor"), int) if rate != 0.0: temp = _tag2obj(stage, _ns("delay"), float) / rate decimation_delay = _read_float_var(temp, FloatWithUncertaintiesAndUnit, unit=True) temp = _tag2obj(stage, _ns("correction"), float) / rate decimation_corr = _read_float_var(temp, FloatWithUncertaintiesAndUnit, unit=True) else: decimation_delay = _read_float_var("inf", FloatWithUncertaintiesAndUnit, unit=True) decimation_corr = _read_float_var("inf", FloatWithUncertaintiesAndUnit, unit=True) decimation_input_sample_rate = \ _read_float_var(rate, Frequency) decimation_offset = int(0) elif(elem_type == "datalogger"): decimation_factor = int(1) decimation_delay = _read_float_var(0.00, FloatWithUncertaintiesAndUnit, unit=True) decimation_corr = _read_float_var(0.00, FloatWithUncertaintiesAndUnit, unit=True) decimation_input_sample_rate = \ _read_float_var(rate, Frequency) decimation_offset = int(0) elif(elem_type == "responsePAZ" or elem_type == "responsePolynomial"): decimation_factor = None decimation_delay = None decimation_corr = None decimation_input_sample_rate = None decimation_offset = None else: raise ValueError("Unknown type of response: " + str(elem_type)) # set up list of for this stage arguments kwargs = { "stage_sequence_number": stage_sequence_number, "input_units": str(input_units), "output_units": str(output_units), "input_units_description": None, "output_units_description": None, "resource_id": None, "resource_id2": resource_id, "stage_gain": stage_gain, "stage_gain_frequency": stage_gain_frequency, "name": name, "description": None, "decimation_input_sample_rate": decimation_input_sample_rate, "decimation_factor": decimation_factor, "decimation_offset": decimation_offset, "decimation_delay": decimation_delay, "decimation_correction": decimation_corr } # Different processing for different types of responses # currently supported: # PAZ # COEFF # FIR # Polynomial response is not supported, could not find example if(elem_type == 'responsePAZ'): # read normalization params normalization_freq = _read_floattype(stage, _ns("normalizationFrequency"), Frequency) normalization_factor = _tag2obj(stage, _ns("normalizationFactor"), float) # Parse the type of the transfer function # A: Laplace (rad) # B: Laplace (Hz) # D: digital (z-transform) pz_transfer_function_type = _tag2obj(stage, _ns("type"), str) if pz_transfer_function_type == 'A': pz_transfer_function_type = 'LAPLACE (RADIANS/SECOND)' elif pz_transfer_function_type == 'B': pz_transfer_function_type = 'LAPLACE (HERTZ)' elif pz_transfer_function_type == 'D': pz_transfer_function_type = 'DIGITAL (Z-TRANSFORM)' else: msg = ("Unknown transfer function code %s. Defaulting to Laplace" "(rad)") % pz_transfer_function_type warnings.warn(msg) pz_transfer_function_type = 'LAPLACE (RADIANS/SECOND)' number_of_zeros = _tag2obj(stage, _ns("numberOfZeros"), int) number_of_poles = _tag2obj(stage, _ns("numberOfPoles"), int) # Parse string of poles and zeros # paz are stored as a string in sc3ml # e.g. (-0.01234,0.01234) (-0.01234,-0.01234) if number_of_zeros > 0: zeros_array = stage.find(_ns("zeros")).text zeros_array = _parse_list_of_complex_string(zeros_array) else: zeros_array = [] if number_of_poles > 0: poles_array = stage.find(_ns("poles")).text poles_array = _parse_list_of_complex_string(poles_array) else: poles_array = [] # Keep counter for pole/zero number cnt = 0 poles = [] zeros = [] for el in poles_array: poles.append(_tag2pole_or_zero(el, cnt)) cnt += 1 for el in zeros_array: zeros.append(_tag2pole_or_zero(el, cnt)) cnt += 1 # Return the paz response return PolesZerosResponseStage( pz_transfer_function_type=pz_transfer_function_type, normalization_frequency=normalization_freq, normalization_factor=normalization_factor, zeros=zeros, poles=poles, **kwargs) elif(elem_type == 'datalogger'): cf_transfer_function_type = "DIGITAL" numerator = [] denominator = [] return CoefficientsTypeResponseStage( cf_transfer_function_type=cf_transfer_function_type, numerator=numerator, denominator=denominator, **kwargs) elif(elem_type == 'responsePolynomial'): # Polynomial response (UNTESTED) # Currently not implemented in ObsPy (20-11-2015) f_low = None f_high = None max_err = None appr_type = _tag2obj(stage, _ns("approximationType"), str) appr_low = _tag2obj(stage, _ns("approximationLowerBound"), float) appr_high = _tag2obj(stage, _ns("approximationUpperBound"), float) coeffs_str = _tag2obj(stage, _ns("coefficients"), str) if coeffs_str is not None: coeffs = coeffs_str.split(" ") coeffs_float = [] i = 0 # pass additional mapping of coefficient counter # so that a proper stationXML can be formatted for c in coeffs: temp = _read_float_var(c, FilterCoefficient, additional_mapping={str("number"): i}) coeffs_float.append(temp) i += 1 return PolynomialResponseStage( approximation_type=appr_type, frequency_lower_bound=f_low, frequency_upper_bound=f_high, approximation_lower_bound=appr_low, approximation_upper_bound=appr_high, maximum_error=max_err, coefficients=coeffs, **kwargs) elif(elem_type == 'responseFIR'): # For the responseFIR obtain the symmetry and # list of coefficients coeffs_str = _tag2obj(stage, _ns("coefficients"), str) coeffs_float = [] if coeffs_str is not None and coeffs_str != 'None': coeffs = coeffs_str.split() i = 0 # pass additional mapping of coefficient counter # so that a proper stationXML can be formatted for c in coeffs: temp = _read_float_var(c, FilterCoefficient, additional_mapping={str("number"): i}) coeffs_float.append(temp) i += 1 # Write the FIR symmetry to what ObsPy expects # A: NONE, # B: ODD, # C: EVEN symmetry = _tag2obj(stage, _ns("symmetry"), str) if(symmetry == 'A'): symmetry = 'NONE' elif(symmetry == 'B'): symmetry = 'ODD' elif(symmetry == 'C'): symmetry = 'EVEN' else: raise ValueError('Unknown symmetry metric; expected A, B, or C') return FIRResponseStage( coefficients=coeffs_float, symmetry=symmetry, **kwargs)
def _read_channel(instrumentation_register, cha_element, _ns): """ reads channel element from sc3ml format :param instrumentation_register: register of instrumentation metadata :param cha_element: channel element :param _ns: namespace """ code = cha_element.get("code") # Information is also kept within the parent <sensorLocation> element sen_loc_element = cha_element.getparent() location_code = sen_loc_element.get("code") # get site info from the <sensorLocation> element longitude = _read_floattype(sen_loc_element, _ns("longitude"), Longitude, datum=True) latitude = _read_floattype(sen_loc_element, _ns("latitude"), Latitude, datum=True) elevation = _read_floattype(sen_loc_element, _ns("elevation"), Distance, unit=True) depth = _read_floattype(cha_element, _ns("depth"), Distance, unit=True) # Set values to 0 if they are is missing (see #1816) if longitude is None: msg = "Sensor is missing longitude information, using 0.0" warnings.warn(msg) longitude = 0 if latitude is None: msg = "Sensor is missing latitude information, using 0.0" warnings.warn(msg) latitude = 0 if elevation is None: msg = "Sensor is missing elevation information, using 0.0" warnings.warn(msg) elevation = 0 if depth is None: msg = "Channel is missing depth information, using 0.0" warnings.warn(msg) depth = 0 channel = obspy.core.inventory.Channel( code=code, location_code=location_code, latitude=latitude, longitude=longitude, elevation=elevation, depth=depth) # obtain the sensorID and link to particular publicID <sensor> element # in the inventory base node sensor_id = cha_element.get("sensor") sensor_element = instrumentation_register["sensors"].get(sensor_id) # obtain the poles and zeros responseID and link to particular # <responsePAZ> publicID element in the inventory base node if (sensor_element is not None and sensor_element.get("response") is not None): response_id = sensor_element.get("response") response_elements = [] for resp_element in instrumentation_register["responses"].values(): found_response = resp_element.get(response_id) if found_response is not None: response_elements.append(found_response) if len(response_elements) == 0: msg = ("Could not find response tag with public ID " "'{}'.".format(response_id)) raise obspy.ObsPyException(msg) elif len(response_elements) > 1: msg = ("Found multiple matching response tags with the same " "public ID '{}'.".format(response_id)) raise obspy.ObsPyException(msg) response_element = response_elements[0] else: response_element = None # obtain the dataloggerID and link to particular <responsePAZ> publicID # element in the inventory base node datalogger_id = cha_element.get("datalogger") data_log_element = \ instrumentation_register["dataloggers"].get(datalogger_id) channel.restricted_status = _get_restricted_status(cha_element, _ns) # There is no further information in the attributes of <stream> # Start and end date are included as tags instead channel.start_date = _tag2obj(cha_element, _ns("start"), obspy.UTCDateTime) channel.end_date = _tag2obj(cha_element, _ns("end"), obspy.UTCDateTime) # Determine sample rate (given is a numerator, denominator) # Assuming numerator is # samples and denominator is # seconds numerator = _tag2obj(cha_element, _ns("sampleRateNumerator"), int) denominator = _tag2obj(cha_element, _ns("sampleRateDenominator"), int) # If numerator is non-zero and denominator zero, will raise # ZeroDivisionError. rate = numerator / denominator if numerator != 0 else 0 channel.sample_rate_ratio_number_samples = numerator channel.sample_rate_ratio_number_seconds = denominator channel.sample_rate = _read_float_var(rate, SampleRate) if sensor_element is not None: channel.sensor = _read_sensor(sensor_element, _ns) if data_log_element is not None: channel.data_logger = _read_datalogger(data_log_element, _ns) temp = _read_floattype(data_log_element, _ns("maxClockDrift"), ClockDrift) if temp is not None: if channel.sample_rate != 0.0: channel.clock_drift_in_seconds_per_sample = \ _read_float_var(temp / channel.sample_rate, ClockDrift) else: msg = "Clock drift division by sample rate of 0: " \ "using sec/sample" warnings.warn(msg) channel.sample_rate = temp channel.azimuth = _read_floattype(cha_element, _ns("azimuth"), Azimuth) channel.dip = _read_floattype(cha_element, _ns("dip"), Dip) match = re.search(r'{([^}]*)}', cha_element.tag) if match: namespace = match.group(1) else: namespace = _get_schema_namespace('0.9') channel.extra = {'format': { 'value': _tag2obj(cha_element, _ns("format"), str), # storage format of channel not supported by StationXML1.1 anymore, # keep it as a foreign tag to be nice if anybody needs to access it 'namespace': namespace}} if channel.sample_rate == 0.0: msg = "Something went hopelessly wrong, found sampling-rate of 0!" warnings.warn(msg) # Begin to collect digital/analogue filter chains # This information is stored as an array in the datalogger element response_fir_id = [] response_paz_id = [] if data_log_element is not None: # Find the decimation element with a particular num/denom decim_element = data_log_element.find(_ns( "decimation[@sampleRateDenominator='" + str(int(denominator)) + "'][@sampleRateNumerator='" + str(int(numerator)) + "']")) analogue_filter_chain = _tag2obj(decim_element, _ns("analogueFilterChain"), str) if analogue_filter_chain is not None: response_paz_id = analogue_filter_chain.split(" ") digital_filter_chain = _tag2obj(decim_element, _ns("digitalFilterChain"), str) if digital_filter_chain is not None: response_fir_id = digital_filter_chain.split(" ") channel.response = _read_response(instrumentation_register['responses'], sensor_element, response_element, cha_element, data_log_element, _ns, channel.sample_rate, response_fir_id, response_paz_id) return channel
def _read_channel(inventory_root, cha_element, _ns): """ reads channel element from sc3ml format :param sta_element: channel element :param _ns: namespace """ code = cha_element.get("code") # Information is also kept within the parent <sensorLocation> element sen_loc_element = cha_element.getparent() location_code = sen_loc_element.get("code") # get site info from the <sensorLocation> element longitude = _read_floattype(sen_loc_element, _ns("longitude"), Longitude, datum=True) latitude = _read_floattype(sen_loc_element, _ns("latitude"), Latitude, datum=True) elevation = _read_floattype(sen_loc_element, _ns("elevation"), Distance, unit=True) depth = _read_floattype(cha_element, _ns("depth"), Distance, unit=True) channel = obspy.core.inventory.Channel(code=code, location_code=location_code, latitude=latitude, longitude=longitude, elevation=elevation, depth=depth) # obtain the sensorID and link to particular publicID <sensor> element # in the inventory base node sensor_id = cha_element.get("sensor") sensor_element = inventory_root.find( _ns("sensor[@publicID='" + sensor_id + "']")) # obtain the poles and zeros responseID and link to particular # <responsePAZ> publicID element in the inventory base node if sensor_element is not None: response_id = sensor_element.get("response") if response_id is not None: resp_type = response_id.split("#")[0] if resp_type == 'ResponsePAZ': search = "responsePAZ[@publicID='" + response_id + "']" response_element = inventory_root.find(_ns(search)) elif resp_type == 'ResponsePolynomial': search = "responsePolynomial[@publicID='" + response_id + "']" response_element = inventory_root.find(_ns(search)) else: response_element = None else: response_element = None # obtain the dataloggerID and link to particular <responsePAZ> publicID # element in the inventory base node datalogger_id = cha_element.get("datalogger") search = "datalogger[@publicID='" + datalogger_id + "']" data_log_element = inventory_root.find(_ns(search)) channel.restricted_status = _get_restricted_status(cha_element, _ns) # There is no further information in the attributes of <stream> # Start and end date are included as tags instead channel.start_date = _tag2obj(cha_element, _ns("start"), obspy.UTCDateTime) channel.end_date = _tag2obj(cha_element, _ns("end"), obspy.UTCDateTime) # Determine sample rate (given is a numerator, denominator) # Assuming numerator is # samples and denominator is # seconds numerator = _tag2obj(cha_element, _ns("sampleRateNumerator"), int) denominator = _tag2obj(cha_element, _ns("sampleRateDenominator"), int) rate = numerator / denominator channel.sample_rate_ratio_number_samples = numerator channel.sample_rate_ratio_number_seconds = denominator channel.sample_rate = _read_float_var(rate, SampleRate) if sensor_element is not None: channel.sensor = _read_sensor(sensor_element, _ns) if data_log_element is not None: channel.data_logger = _read_datalogger(data_log_element, _ns) temp = _read_floattype(data_log_element, _ns("maxClockDrift"), ClockDrift) if channel.sample_rate != 0.0: channel.clock_drift_in_seconds_per_sample = \ _read_float_var(temp / channel.sample_rate, ClockDrift) else: msg = "Clock drift division by sample rate of 0: using sec/sample" warnings.warn(msg) channel.sample_rate = temp channel.azimuth = _read_floattype(cha_element, _ns("azimuth"), Azimuth) channel.dip = _read_floattype(cha_element, _ns("dip"), Dip) channel.storage_format = _tag2obj(cha_element, _ns("format"), str) if channel.sample_rate == 0.0: msg = "Something went hopelessly wrong, found sampling-rate of 0!" warnings.warn(msg) # Begin to collect digital/analogue filter chains # This information is stored as an array in the datalogger element response_fir_id = [] response_paz_id = [] if data_log_element is not None: # Find the decimation element with a particular num/denom decim_element = data_log_element.find( _ns("decimation[@sampleRateDenominator='" + str(int(denominator)) + "'][@sampleRateNumerator='" + str(int(numerator)) + "']")) analogue_filter_chain = _tag2obj(decim_element, _ns("analogueFilterChain"), str) if analogue_filter_chain is not None: response_paz_id = analogue_filter_chain.split(" ") digital_filter_chain = _tag2obj(decim_element, _ns("digitalFilterChain"), str) if digital_filter_chain is not None: response_fir_id = digital_filter_chain.split(" ") channel.response = _read_response(inventory_root, sensor_element, response_element, cha_element, data_log_element, _ns, channel.sample_rate, response_fir_id, response_paz_id) return channel
def _read_channel(inventory_root, cha_element, _ns): """ reads channel element from sc3ml format :param sta_element: channel element :param _ns: namespace """ code = cha_element.get("code") # Information is also kept within the parent <sensorLocation> element sen_loc_element = cha_element.getparent() location_code = sen_loc_element.get("code") # get site info from the <sensorLocation> element longitude = _read_floattype(sen_loc_element, _ns("longitude"), Longitude, datum=True) latitude = _read_floattype(sen_loc_element, _ns("latitude"), Latitude, datum=True) elevation = _read_floattype(sen_loc_element, _ns("elevation"), Distance, unit=True) depth = _read_floattype(cha_element, _ns("depth"), Distance, unit=True) # Set values to 0 if they are is missing (see #1816) if longitude is None: msg = "Sensor is missing longitude information, using 0.0" warnings.warn(msg) longitude = 0 if latitude is None: msg = "Sensor is missing latitude information, using 0.0" warnings.warn(msg) latitude = 0 if elevation is None: msg = "Sensor is missing elevation information, using 0.0" warnings.warn(msg) elevation = 0 if depth is None: msg = "Channel is missing depth information, using 0.0" warnings.warn(msg) depth = 0 channel = obspy.core.inventory.Channel( code=code, location_code=location_code, latitude=latitude, longitude=longitude, elevation=elevation, depth=depth) # obtain the sensorID and link to particular publicID <sensor> element # in the inventory base node sensor_id = cha_element.get("sensor") sensor_element = inventory_root.find(_ns("sensor[@publicID='" + sensor_id + "']")) # obtain the poles and zeros responseID and link to particular # <responsePAZ> publicID element in the inventory base node if (sensor_element is not None and sensor_element.get("response") is not None): response_id = sensor_element.get("response") response_elements = [] for resp_type in ['responsePAZ', 'responsePolynomial']: search = "{}[@publicID='{}']".format(resp_type, response_id) response_elements += inventory_root.findall(_ns(search)) if len(response_elements) == 0: msg = ("Could not find response tag with public ID " "'{}'.".format(response_id)) raise obspy.ObsPyException(msg) elif len(response_elements) > 1: msg = ("Found multiple matching response tags with the same " "public ID '{}'.".format(response_id)) raise obspy.ObsPyException(msg) response_element = response_elements[0] else: response_element = None # obtain the dataloggerID and link to particular <responsePAZ> publicID # element in the inventory base node datalogger_id = cha_element.get("datalogger") search = "datalogger[@publicID='" + datalogger_id + "']" data_log_element = inventory_root.find(_ns(search)) channel.restricted_status = _get_restricted_status(cha_element, _ns) # There is no further information in the attributes of <stream> # Start and end date are included as tags instead channel.start_date = _tag2obj(cha_element, _ns("start"), obspy.UTCDateTime) channel.end_date = _tag2obj(cha_element, _ns("end"), obspy.UTCDateTime) # Determine sample rate (given is a numerator, denominator) # Assuming numerator is # samples and denominator is # seconds numerator = _tag2obj(cha_element, _ns("sampleRateNumerator"), int) denominator = _tag2obj(cha_element, _ns("sampleRateDenominator"), int) # If numerator is zero, set rate to zero irrespective of the denominator. # If numerator is non-zero and denominator zero, will raise # ZeroDivisionError. rate = numerator / denominator if numerator != 0 else 0 channel.sample_rate_ratio_number_samples = numerator channel.sample_rate_ratio_number_seconds = denominator channel.sample_rate = _read_float_var(rate, SampleRate) if sensor_element is not None: channel.sensor = _read_sensor(sensor_element, _ns) if data_log_element is not None: channel.data_logger = _read_datalogger(data_log_element, _ns) temp = _read_floattype(data_log_element, _ns("maxClockDrift"), ClockDrift) if temp is not None: if channel.sample_rate != 0.0: channel.clock_drift_in_seconds_per_sample = \ _read_float_var(temp / channel.sample_rate, ClockDrift) else: msg = "Clock drift division by sample rate of 0: " \ "using sec/sample" warnings.warn(msg) channel.sample_rate = temp channel.azimuth = _read_floattype(cha_element, _ns("azimuth"), Azimuth) channel.dip = _read_floattype(cha_element, _ns("dip"), Dip) channel.storage_format = _tag2obj(cha_element, _ns("format"), str) if channel.sample_rate == 0.0: msg = "Something went hopelessly wrong, found sampling-rate of 0!" warnings.warn(msg) # Begin to collect digital/analogue filter chains # This information is stored as an array in the datalogger element response_fir_id = [] response_paz_id = [] if data_log_element is not None: # Find the decimation element with a particular num/denom decim_element = data_log_element.find(_ns( "decimation[@sampleRateDenominator='" + str(int(denominator)) + "'][@sampleRateNumerator='" + str(int(numerator)) + "']")) analogue_filter_chain = _tag2obj(decim_element, _ns("analogueFilterChain"), str) if analogue_filter_chain is not None: response_paz_id = analogue_filter_chain.split(" ") digital_filter_chain = _tag2obj(decim_element, _ns("digitalFilterChain"), str) if digital_filter_chain is not None: response_fir_id = digital_filter_chain.split(" ") channel.response = _read_response(inventory_root, sensor_element, response_element, cha_element, data_log_element, _ns, channel.sample_rate, response_fir_id, response_paz_id) return channel
def _read_response_stage(stage, _ns, rate, stage_sequence_number, input_units, output_units): # Strip the namespace to get the element name (response type) elem_type = stage.tag.split("}")[1] # Get the stage gain and frequency: 0 and 0.00 per default stage_gain = _tag2obj(stage, _ns("gain"), float) or 0 stage_gain_frequency = _tag2obj(stage, _ns("gainFrequency"), float) or 0.00 # Get the stage name name = stage.get("name") if name is not None: name = str(name) # And the public resource identifier resource_id = stage.get("publicID") if resource_id is not None: resource_id = str(resource_id) # Set up decimation parameters decimation = { "factor": None, "delay": None, "correction": None, "rate": None, "offset": None } # Skip decimation for analogue outputs # Since 0.10 ResponsePAZ can have a decimation attributes if output_units != "V": # Get element or default value decimation['factor'] = _tag2obj(stage, _ns("decimationFactor"), int) or 1 decimation['delay'] = _tag2obj(stage, _ns("delay"), float) or 0 decimation["correction"] = _tag2obj(stage, _ns("correction"), float) or 0 decimation['offset'] = _tag2obj(stage, _ns("offset"), float) or 0 decimation['rate'] = _read_float_var(rate, Frequency) # Decimation delay/correction need to be normalized if rate != 0.0: if decimation['delay'] is not None: decimation['delay'] = \ _read_float_var(decimation['delay'] / rate, FloatWithUncertaintiesAndUnit, unit=True) if decimation['correction'] is not None: decimation['correction'] = \ _read_float_var(decimation['correction'] / rate, FloatWithUncertaintiesAndUnit, unit=True) # Set up list of for this stage arguments kwargs = { "stage_sequence_number": stage_sequence_number, "input_units": str(input_units), "output_units": str(output_units), "input_units_description": None, "output_units_description": None, "resource_id": None, "resource_id2": resource_id, "stage_gain": stage_gain, "stage_gain_frequency": stage_gain_frequency, "name": name, "description": None, "decimation_input_sample_rate": decimation['rate'], "decimation_factor": decimation['factor'], "decimation_offset": decimation['offset'], "decimation_delay": decimation['delay'], "decimation_correction": decimation['correction'] } # Different processing for different types of responses # currently supported: PAZ, COEFF, FIR # Polynomial response is not supported, could not find example if (elem_type == 'responsePAZ'): # read normalization params normalization_freq = _read_floattype(stage, _ns("normalizationFrequency"), Frequency) normalization_factor = _tag2obj(stage, _ns("normalizationFactor"), float) # Parse the type of the transfer function # A: Laplace (rad) # B: Laplace (Hz) # D: digital (z-transform) pz_transfer_function_type = _tag2obj(stage, _ns("type"), str) pz_transfer_function_type = \ _map_transfer_type(pz_transfer_function_type) # Parse string of poles and zeros # paz are stored as a string in scxml # e.g. (-0.01234,0.01234) (-0.01234,-0.01234) zeros_array = stage.find(_ns("zeros")) poles_array = stage.find(_ns("poles")) if zeros_array is not None and zeros_array.text is not None: zeros_array = _parse_list_of_complex_string(zeros_array.text) else: zeros_array = [] if poles_array is not None and poles_array.text is not None: poles_array = _parse_list_of_complex_string(poles_array.text) else: poles_array = [] # Keep counter for pole/zero number cnt = 0 poles = [] zeros = [] for el in poles_array: poles.append(_tag2pole_or_zero(el, cnt)) cnt += 1 for el in zeros_array: zeros.append(_tag2pole_or_zero(el, cnt)) cnt += 1 # Return the paz response return PolesZerosResponseStage( pz_transfer_function_type=pz_transfer_function_type, normalization_frequency=normalization_freq, normalization_factor=normalization_factor, zeros=zeros, poles=poles, **kwargs) # For IIR filters reuse the PolesZerosResponseStage elif (elem_type == 'responseIIR'): pz_transfer_function_type = _tag2obj(stage, _ns("type"), str) pz_transfer_function_type = _map_transfer_type( pz_transfer_function_type) numerators = stage.find(_ns("numerators")).text.split(" ") denominators = stage.find(_ns("denominators")).text.split(" ") numerators = list(map(lambda x: float(x), numerators)) denominators = list(map(lambda x: float(x), denominators)) # Convert linear filter to pole, zero, gain repr. # See #2004 @andres-h zeros, poles, gain = \ (np.round(ele, 6) for ele in tf2zpk(numerators, denominators)) msg = "ResponseIIR is not fully tested in ObsPy. Please be cautious" warnings.warn(msg) return PolesZerosResponseStage( pz_transfer_function_type=pz_transfer_function_type, normalization_frequency=0, normalization_factor=1, zeros=zeros, poles=poles, **kwargs) # Datalogger element: V => Counts # Set empty coefficients and hard code as digital elif (elem_type == "datalogger"): return CoefficientsTypeResponseStage( cf_transfer_function_type="DIGITAL", numerator=[], denominator=[], **kwargs) elif (elem_type == 'responsePolynomial'): # Polynomial response (UNTESTED) # Currently not implemented in ObsPy (20-11-2015) f_low = None f_high = None max_err = None appr_type = _tag2obj(stage, _ns("approximationType"), str) appr_low = _tag2obj(stage, _ns("approximationLowerBound"), float) appr_high = _tag2obj(stage, _ns("approximationUpperBound"), float) coeffs_str = _tag2obj(stage, _ns("coefficients"), str) if coeffs_str is not None: coeffs = coeffs_str.strip().split(" ") coeffs_float = [] i = 0 # pass additional mapping of coefficient counter # so that a proper stationXML can be formatted for c in coeffs: temp = _read_float_var(c, FilterCoefficient, additional_mapping={str("number"): i}) coeffs_float.append(temp) i += 1 return PolynomialResponseStage(approximation_type=appr_type, frequency_lower_bound=f_low, frequency_upper_bound=f_high, approximation_lower_bound=appr_low, approximation_upper_bound=appr_high, maximum_error=max_err, coefficients=coeffs, **kwargs) elif (elem_type == 'responseFIR'): # For the responseFIR obtain the symmetry and # list of coefficients coeffs_str = _tag2obj(stage, _ns("coefficients"), str) coeffs_float = [] if coeffs_str is not None and coeffs_str != 'None': coeffs = coeffs_str.strip().split(" ") i = 0 # pass additional mapping of coefficient counter # so that a proper stationXML can be formatted for c in coeffs: temp = _read_float_var(c, FilterCoefficient, additional_mapping={str("number"): i}) coeffs_float.append(temp) i += 1 # Write the FIR symmetry to what ObsPy expects # A: NONE, # B: ODD, # C: EVEN symmetry = _tag2obj(stage, _ns("symmetry"), str) if (symmetry == 'A'): symmetry = 'NONE' elif (symmetry == 'B'): symmetry = 'ODD' elif (symmetry == 'C'): symmetry = 'EVEN' else: raise ValueError('Unknown symmetry metric; expected A, B, or C') return FIRResponseStage(coefficients=coeffs_float, symmetry=symmetry, **kwargs) elif (elem_type == 'responseFAP'): data = _tag2obj(stage, _ns("tuples"), str) data = np.array(data.split(), dtype=np.float64) freq, amp, phase = data.reshape((-1, 3)).T elements = [] for freq_, amp_, phase_ in zip(freq, amp, phase): elements.append(ResponseListElement(freq_, amp_, phase_)) return ResponseListResponseStage(response_list_elements=elements, **kwargs)