Esempio n. 1
0
 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)
Esempio n. 2
0
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
Esempio n. 3
0
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)