def test_custom_types_init(self): """ Test initializations that involve custom decimal types like `ComplexWithUncertainties`. """ # initializing poles / zeros from native types should work poles = [1 + 1j, 1, 1j] zeros = [2 + 3j, 2, 3j] stage = PolesZerosResponseStage(1, 1, 1, "", "", "LAPLACE (HERTZ)", 1, zeros, poles) self.assertEqual(type(stage.zeros[0]), ComplexWithUncertainties) self.assertEqual(type(stage.poles[0]), ComplexWithUncertainties) self.assertEqual(stage.poles, poles) self.assertEqual(stage.zeros, zeros)
def _get_pzrs(resp, verbose=False): """ Get PolesZerosResponseStages Like obspy get_paz(), except that. if there is more than one PZ stage, combines them into one Does not handle decimation, resource_id, resource_id2 Returns LAST stage's stage_sequence number :param resp: obspy Response object :returns: obspy PoleZeroResponse stage (stage_sequence_number = LAST paz stage) """ pz_stages = [ copy.deepcopy(stage) for stage in resp.response_stages if isinstance(stage, PolesZerosResponseStage) ] if len(pz_stages) == 0: print("No PolesZerosResponseStage found.") pz_out = PolesZerosResponseStage(normalization_factor=1., stage_gain=1., poles=[], zeros=[]) elif len(pz_stages) == 1: pz_out = pz_stages[0] else: if verbose: print(f"Combining {len(pz_out):d} PolesZerosResponseStages") pz_out = pz_stages[0] for pz in pz_stages[1:]: if _is_compatible(pz_out, pz): if pz.description is not None: pz_out.description += ' + ' + pz.description if not pz_out.name: if pz.name: pz_out.name = ' + ' + pz.name elif pz.name: pz_out.name += ' + ' + pz.name pz_out.normalization_factor *= pz.normalization_factor pz_out.output_units = pz.output_units pz_out.output_units_description = pz.output_units_description pz_out.stage_gain *= pz.stage_gain pz_out.poles.extend(pz.poles) pz_out.zeros.extend(pz.zeros) pz_out.stage_sequence_number = pz.stage_sequence_number else: print('Incompatible stages... quitting') return None return pz_out
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)