class Blockette012(Blockette): """ Blockette 012: Volume Time Span Index Blockette. This blockette forms an index to the time spans that encompass the actual data. One index entry exists for each time span recorded later in the volume. Time spans are not used for field station type volumes. There should be one entry in this index for each time span control header. (For more information, see the notes for blockettes [70], [73], and [74].) Sample: 012006300011992,001,00:00:00.0000~1992,002,00:00:00.0000~000014 """ id = 12 name = "Volume Timespan Index" fields = [ Integer(3, "Number of spans in table", 4), # REPEAT fields 4 — 6 for the Number of spans in table: Loop("Timespan", "Number of spans in table", [ VariableString(4, "Beginning of span", 1, 22, 'T'), VariableString(5, "End of span", 1, 22, 'T'), Integer( 6, "Sequence number of time span header", 6, ignore=True) ], optional=True), ]
class Blockette030(Blockette): """ Blockette 030: Data Format Dictionary Blockette. All volumes, with the exception of miniSEED data records, must have a Data Format Dictionary Blockette [30]. Each Channel Identifier Blockette [52] has a reference (field 16) back to a Data Format Dictionary Blockette [30], so that SEED reading programs will know how to decode data for the channels. Because every kind of data format requires an entry in the Data Format Dictionary Blockette [30], each recording network needs to list entries for each data format, if a heterogeneous mix of data formats are included in a volume. This data format dictionary is used to decompress the data correctly. Sample: 0300086CDSN Gain-Ranged Format~000200104M0~W2 D0-13 A-8191~D1415~ P0:#0,1:#2,2:#4,3:#7~ """ id = 30 name = "Data Format Dictionary" fields = [ VariableString(3, "Short descriptive name", 1, 50, 'UNLPS'), Integer(4, "Data format identifier code", 4), Integer(5, "Data family type", 3), Integer(6, "Number of decoder keys", 2), # REPEAT field 7 for the Number of decoder keys: Loop("Decoder keys", "Number of decoder keys", [VariableString(7, "Decoder keys", flags='UNLPS')], omit_tag=True), ]
class Blockette011(Blockette): """ Blockette 011: Volume Station Header Index Blockette. This is the index to the Station Identifier Blockettes [50] that appear later in the volume. This blockette refers to each station described in the station header section. Sample: 0110054004AAK 000003ANMO 000007ANTO 000010BJI 000012 """ id = 11 name = "Volume Station Header Index" fields = [ Integer(3, "Number of stations", 3), # REPEAT fields 4 — 5 for the Number of stations: Loop( "Station identifier", "Number of stations", [ FixedString(4, "Station identifier code", 5), Integer(5, "Sequence number of station header", 6, ignore=True) ], repeat_title=True) ]
class Blockette061(Blockette): """ Blockette 061: FIR Response Blockette. The FIR blockette is used to specify FIR (Finite Impulse Response) digital filter coefficients. It is an alternative to blockette [54] when specifying FIR filters. The blockette recognizes the various forms of filter symmetry and can exploit them to reduce the number of factors specified to the blockette. In July 2007, the FDSN adopted a convention that requires the coefficients to be listed in forward time order. As a reference, minimum-phase filters (which are asymmetric) should be written with the largest values near the beginning of the coefficient list. """ id = 61 name = "FIR Response" fields = [ Integer(3, "Stage sequence number", 2), VariableString(4, "Response Name", 1, 25, 'UN_'), FixedString(5, "Symmetry Code", 1, 'U'), Integer(6, "Signal In Units", 3, xpath=34), Integer(7, "Signal Out Units", 3, xpath=34), Integer(8, "Number of Coefficients", 4), #REPEAT field 9 for the Number of Coefficients Loop("FIR Coefficient", "Number of Coefficients", [Float(9, "FIR Coefficient", 14, mask='%+1.7e')], flat=True), ] def getRESP(self, station, channel, abbreviations): """ Returns RESP string. """ out = RESP % (station, channel, self.stage_sequence_number, self.symmetry_code, LookupCode(abbreviations, 34, 'unit_name', 'unit_lookup_code', self.signal_in_units), LookupCode(abbreviations, 34, 'unit_description', 'unit_lookup_code', self.signal_in_units), LookupCode(abbreviations, 34, 'unit_name', 'unit_lookup_code', self.signal_out_units), LookupCode(abbreviations, 34, 'unit_description', 'unit_lookup_code', self.signal_out_units), self.number_of_coefficients) if self.number_of_coefficients > 1: out += '#\t\tNumerator coefficients:\n' out += '#\t\t i, coefficient\n' for _i in range(self.number_of_coefficients): out += 'B061F09 %4s %13s\n' % \ (_i, formatRESP(self.FIR_coefficient[_i], 6)) elif self.number_of_coefficients == 1: out += '#\t\tNumerator coefficients:\n' out += '#\t\t i, coefficient\n' out += 'B061F09 %4s %13s\n' % \ (0, formatRESP(self.FIR_coefficient, 6)) out += '#\t\t\n' return out.encode()
class Blockette041(Blockette): """ Blockette 041: FIR Dictionary Blockette. The FIR blockette is used to specify FIR (Finite Impulse Response) digital filter coefficients. It is an alternative to blockette [44] when specifying FIR filters. The blockette recognizes the various forms of filter symmetry and can exploit them to reduce the number of factors specified in the blockette. See Response (Coefficients) Blockette [54] for more information. """ id = 41 name = "FIR Dictionary" fields = [ Integer(3, "Response Lookup Key", 4), VariableString(4, "Response Name", 1, 25, 'UN_'), FixedString(5, "Symmetry Code", 1, 'U'), Integer(6, "Signal In Units", 3, xpath=34), Integer(7, "Signal Out Units", 3, xpath=34), Integer(8, "Number of Factors", 4), #REPEAT field 9 for the Number of Factors Loop("FIR Coefficient", "Number of Factors", [Float(9, "FIR Coefficient", 14, mask='%+1.7e')], flat=True), ] def parseSEED(self, data, expected_length=0): """ If number of FIR coefficients are larger than maximal blockette size of 9999 chars a follow up blockette with the same blockette id and response lookup key is expected - this is checked here. """ # convert to stream for test issues if isinstance(data, basestring): expected_length = len(data) data = StringIO(data) # get current lookup key pos = data.tell() data.read(7) global_lookup_key = int(data.read(4)) data.seek(pos) # read first blockette temp = StringIO() temp.write(data.read(expected_length)) # check next blockettes while True: # save position pos = data.tell() try: blockette_id = int(data.read(3)) except ValueError: break if blockette_id != 41: # different blockette id -> break break blockette_length = int(data.read(4)) lookup_key = int(data.read(4)) if lookup_key != global_lookup_key: # different lookup key -> break break # ok follow up blockette found - skip some unneeded fields self.fields[1].read(data) self.fields[2].read(data) self.fields[3].read(data) self.fields[4].read(data) self.fields[5].read(data) # remaining length in current blockette length = pos - data.tell() + blockette_length # read follow up blockette and append it to temporary blockette temp.write(data.read(length)) # reposition file pointer data.seek(pos) # parse new combined temporary blockette temp.seek(0) Blockette.parseSEED(self, temp, expected_length=temp.len) def parseXML(self, xml_doc, *args, **kwargs): if self.xseed_version == '1.0': xml_doc.find('fir_coefficient').tag = 'FIR_coefficient' Blockette.parseXML(self, xml_doc, *args, **kwargs) def getXML(self, *args, **kwargs): xml = Blockette.getXML(self, *args, **kwargs) if self.xseed_version == '1.0': xml.find('FIR_coefficient').tag = 'fir_coefficient' return xml def getRESP(self, station, channel, abbreviations): """ Returns RESP string. """ string = \ '#\t\t+ +--------------------------------+' + \ ' +\n' + \ '#\t\t+ | FIR response,' + \ '%6s ch %s | +\n' % (station, channel) + \ '#\t\t+ +--------------------------------+' + \ ' +\n' + \ '#\t\t\n' + \ 'B041F05 Symmetry type: %s\n' \ % self.symmetry_code + \ 'B041F06 Response in units lookup: %s - %s\n'\ % (LookupCode(abbreviations, 34, 'unit_name', 'unit_lookup_code', self.signal_in_units), LookupCode(abbreviations, 34, 'unit_description', 'unit_lookup_code', self.signal_in_units)) + \ 'B041F07 Response out units lookup: %s - %s\n'\ % (LookupCode(abbreviations, 34, 'unit_name', 'unit_lookup_code', self.signal_out_units), LookupCode(abbreviations, 34, 'unit_description', 'unit_lookup_code', self.signal_out_units)) + \ 'B041F08 Number of numerators: %s\n' \ % self.number_of_factors if self.number_of_factors > 1: string += '#\t\tNumerator coefficients:\n' + \ '#\t\t i, coefficient\n' for _i in xrange(self.number_of_factors): string += 'B041F09 %4s %13s\n' \ % (_i, formatRESP(self.FIR_coefficient[_i], 6)) elif self.number_of_factors == 1: string += '#\t\tNumerator coefficients:\n' + \ '#\t\t i, coefficient\n' string += 'B041F09 %4s %13s\n' \ % (0, formatRESP(self.FIR_coefficient, 6)) string += '#\t\t\n' return string
class Blockette043(Blockette): """ Blockette 043: Response (Poles & Zeros) Dictionary Blockette. See Response (Poles & Zeros) Blockette [53] for more information. """ id = 43 name = "Response Poles and Zeros Dictionary" fields = [ Integer(3, "Response Lookup Key", 4), VariableString(4, "Response Name", 1, 25, 'UN_'), FixedString(5, "Response type", 1, 'U'), Integer(6, "Stage signal input units", 3, xpath=34), Integer(7, "Stage signal output units", 3, xpath=34), Float(8, "A0 normalization factor", 12, mask='%+1.5e'), Float(9, "Normalization frequency", 12, mask='%+1.5e'), Integer(10, "Number of complex zeros", 3), # REPEAT fields 11 — 14 for the Number of complex zeros: Loop('Complex zero', "Number of complex zeros", [ Float(11, "Real zero", 12, mask='%+1.5e'), Float(12, "Imaginary zero", 12, mask='%+1.5e'), Float(13, "Real zero error", 12, mask='%+1.5e'), Float(14, "Imaginary zero error", 12, mask='%+1.5e') ]), Integer(15, "Number of complex poles", 3), # REPEAT fields 16 — 19 for the Number of complex poles: Loop('Complex pole', "Number of complex poles", [ Float(16, "Real pole", 12, mask='%+1.5e'), Float(16, "Imaginary pole", 12, mask='%+1.5e'), Float(18, "Real pole error", 12, mask='%+1.5e'), Float(19, "Imaginary pole error", 12, mask='%+1.5e') ]) ] # Changes the name of the blockette because of an error in XSEED 1.0 def getXML(self, *args, **kwargs): xml = Blockette.getXML(self, *args, **kwargs) if self.xseed_version == '1.0': xml.tag = 'response_poles_and_zeros' return xml def getRESP(self, station, channel, abbreviations): """ Returns RESP string. """ # Field five needs some extra parsing. field_five_dict = { 'A': 'A [Laplace Transform (Rad/sec)]', 'B': 'B [Analog (Hz)]', 'C': 'C [Composite]', 'D': 'D [Digital (Z-transform)]' } string = \ '#\t\t+ ' + \ '+-----------------------------------------' + \ '---+ +\n' + \ '#\t\t+ | Response (Poles & Zeros),' + \ '%6s ch %s | +\n' % (station, channel) + \ '#\t\t+ ' + \ '+-----------------------------------------' + \ '---+ +\n' + \ '#\t\t\n' + \ 'B043F05 Response type: %s\n' \ % field_five_dict[self.response_type] + \ 'B043F06 Response in units lookup: %s\n' \ % Blockette34Lookup(abbreviations, self.stage_signal_input_units) + \ 'B043F07 Response out units lookup: %s\n' \ % Blockette34Lookup(abbreviations, self.stage_signal_output_units) + \ 'B043F08 A0 normalization factor: %G\n'\ % self.A0_normalization_factor + \ 'B043F09 Normalization frequency: %G\n'\ % self.normalization_frequency + \ 'B043F10 Number of zeroes: %s\n'\ % self.number_of_complex_zeros + \ 'B043F15 Number of poles: %s\n'\ % self.number_of_complex_poles + \ '#\t\tComplex zeroes:\n' + \ '#\t\t i real imag real_error imag_error\n' if self.number_of_complex_zeros > 0: if self.number_of_complex_zeros != 1: # Loop over all zeros. for _i in range(self.number_of_complex_zeros): string += 'B043F11-14 %4s %13s %13s %13s %13s\n' % ( _i, formatRESP(self.real_zero[_i], 6), formatRESP(self.imaginary_zero[_i], 6), formatRESP(self.real_zero_error[_i], 6), formatRESP(self.imaginary_zero_error[_i], 6)) else: string += 'B043F11-14 %4s %13s %13s %13s %13s\n' % ( 0, formatRESP(self.real_zero, 6), formatRESP(self.imaginary_zero, 6), formatRESP(self.real_zero_error, 6), formatRESP(self.imaginary_zero_error, 6)) string += '#\t\tComplex poles:\n' + \ '#\t\t i real imag real_error imag_error\n' if self.number_of_complex_poles > 0: if self.number_of_complex_poles != 1: # Loop over all poles. for _i in range(self.number_of_complex_poles): string += 'B043F16-19 %4s %13s %13s %13s %13s\n' % ( _i, formatRESP(self.real_pole[_i], 6), formatRESP(self.imaginary_pole[_i], 6), formatRESP(self.real_pole_error[_i], 6), formatRESP(self.imaginary_pole_error[_i], 6)) else: string += 'B043F16-19 %4s %13s %13s %13s %13s\n' % ( 0, formatRESP(self.real_pole, 6), formatRESP(self.imaginary_pole, 6), formatRESP(self.real_pole_error, 6), formatRESP(self.imaginary_pole_error, 6)) string += '#\t\t\n' return string
class Blockette058(Blockette): """ Blockette 058: Channel Sensitivity/Gain Blockette. When used as a gain (stage ≠ 0), this blockette is the gain for this stage at the given frequency. Different stages may be at different frequencies. However, it is strongly recommended that the same frequency be used in all stages of a cascade, if possible. When used as a sensitivity(stage=0), this blockette is the sensitivity (in counts per ground motion) for the entire channel at a given frequency, and is also referred to as the overall gain. The frequency here may be different from the frequencies in the gain specifications, but should be the same if possible. If you use cascading (more than one filter stage), then SEED requires a gain for each stage. A final sensitivity (Blockette [58], stage = 0, is required. If you do not use cascading (only one stage), then SEED must see a gain, a sensitivity, or both. Sample: 0580035 3 3.27680E+03 0.00000E+00 0 """ id = 58 name = "Channel Sensitivity Gain" fields = [ Integer(3, "Stage sequence number", 2), Float(4, "Sensitivity gain", 12, mask='%+1.5e'), Float(5, "Frequency", 12, mask='%+1.5e'), Integer(6, "Number of history values", 2), # REPEAT fields 7 — 9 for the Number of history values: Loop('History', "Number of history values", [ Float(7, "Sensitivity for calibration", 12, mask='%+1.5e'), Float(8, "Frequency of calibration sensitivity", 12, mask='%+1.5e'), VariableString(9, "Time of above calibration", 1, 22, 'T') ]) ] def getRESP(self, station, channel, abbreviations): """ Returns RESP string. """ # This blockette can result in two different RESPs. blkt_type = self.stage_sequence_number if blkt_type != 0: string = \ '#\t\t+ +-------------------------------' + \ '--------+ +\n' + \ '#\t\t+ | Channel Gain,' + \ '%6s ch %s | +\n' % (station, channel) +\ '#\t\t+ +-------------------------------' + \ '--------+ +\n' else: string = \ '#\t\t+ +--------------------------------' + \ '-------+ +\n' + \ '#\t\t+ | Channel Sensitivity,' + \ '%6s ch %s | +\n' % (station, channel) + \ '#\t\t+ +--------------------------------' + \ '-------+ +\n' string += '#\t\t\n' + \ 'B058F03 Stage sequence number: %s\n' \ % blkt_type if blkt_type != 0: string += \ 'B058F04 Gain: %s\n' \ % formatRESP(self.sensitivity_gain, 6) + \ 'B058F05 Frequency of gain: %s HZ\n' \ % formatRESP(self.frequency, 6) else: string += \ 'B058F04 Sensitivity: %s\n' \ % formatRESP(self.sensitivity_gain, 6) + \ 'B058F05 Frequency of sensitivity: %s HZ\n' \ % formatRESP(self.frequency, 6) string += \ 'B058F06 Number of calibrations: %s\n' \ % self.number_of_history_values if self.number_of_history_values > 1: string += \ '#\t\tCalibrations:\n' + \ '#\t\t i, sensitivity, frequency, time of calibration\n' for _i in xrange(self.number_of_history_values): string += \ 'B058F07-08 %2s %13s %13s %s\n' \ % (formatRESP(self.sensitivity_for_calibration[_i], 6), formatRESP( self.frequency_of_calibration_sensitivity[_i], 6), self.time_of_above_calibration[_i].formatSEED()) elif self.number_of_history_values == 1: string += \ '#\t\tCalibrations:\n' + \ '#\t\t i, sensitivity, frequency, time of calibration\n' + \ 'B058F07-08 0 %13s %13s %s\n' \ % (formatRESP(self.sensitivity_for_calibration, 6), formatRESP(self.frequency_of_calibration_sensitivity, 6), self.time_of_above_calibration.formatSEED()) string += '#\t\t\n' return string
class Blockette044(Blockette): """ Blockette 044: Response (Coefficients) Dictionary Blockette. See Response (Coefficients) Dictionary Blockette [54] for more information. """ id = 44 name = "Response Coefficients Dictionary" fields = [ Integer(3, "Response Lookup Key", 4), VariableString(4, "Response Name", 1, 25, 'UN_'), FixedString(5, "Response type", 1, 'U'), Integer(6, "Signal input units", 3, xpath=34), Integer(7, "Signal output units", 3, xpath=34), Integer(8, "Number of numerators", 4), # REPEAT fields 9 - 10 for the Number of numerators: Loop('Numerators', "Number of numerators", [ Float(9, "Numerator coefficient", 12, mask='%+1.5e'), Float(10, "Numerator error", 12, mask='%+1.5e') ], flat=True), Integer(11, "Number of denominators", 4), # REPEAT fields 12 — 13 for the Number of denominators: Loop('Denominators', "Number of denominators", [ Float(12, "Denominator coefficient", 12, mask='%+1.5e'), Float(13, "Denominator error", 12, mask='%+1.5e') ], flat=True) ] # Changes the name of the blockette because of an error in XSEED 1.0 def getXML(self, *args, **kwargs): xml = Blockette.getXML(self, *args, **kwargs) if self.xseed_version == '1.0': xml.tag = 'response_coefficients' return xml def getRESP(self, station, channel, abbreviations): """ Returns RESP string. """ string = \ '#\t\t+ +----------------------------------------' +\ '---+ +\n' + \ '#\t\t+ | Response (Coefficients),' + \ '%6s ch %s | +\n' % (station, channel) + \ '#\t\t+ +----------------------------------------' +\ '---+ +\n' + \ '#\t\t\n' + \ 'B044F05 Response type: %s\n' \ % self.response_type + \ 'B044F06 Response in units lookup: %s\n'\ % Blockette34Lookup(abbreviations, self.signal_input_units) + \ 'B044F07 Response out units lookup: %s\n'\ % Blockette34Lookup(abbreviations, self.signal_output_units) + \ 'B044F08 Number of numerators: %s\n' \ % self.number_of_numerators + \ 'B044F11 Number of denominators: %s\n' \ % self.number_of_denominators + \ '#\t\tNumerator coefficients:\n' + \ '#\t\t i, coefficient, error\n' if self.number_of_numerators: string += \ '#\t\tNumerator coefficients:\n' + \ '#\t\t i, coefficient, error\n' if self.number_of_numerators > 1: # Loop over all zeros. for _i in range(self.number_of_numerators): string += 'B044F09-10 %3s %13s %13s\n' % ( _i, formatRESP(self.numerator_coefficient[_i], 6), formatRESP(self.numerator_error[_i], 6)) else: string += 'B044F09-10 %3s %13s %13s\n' % ( 0, formatRESP(self.numerator_coefficient, 6), formatRESP(self.numerator_error, 6)) if self.number_of_denominators: string += \ '#\t\tDenominator coefficients:\n' + \ '#\t\t i, coefficient, error\n' if self.number_of_denominators > 1: # Loop over all zeros. for _i in range(self.number_of_numerators): string += 'B044F12-13 %3s %13s %13s\n' % ( _i, formatRESP(self.denominator_coefficient[_i], 6), formatRESP(self.denominator_error[_i], 6)) else: string += 'B044F12-13 %3s %13s %13s\n' % ( 0, formatRESP(self.denominator_coefficient, 6), formatRESP(self.denominator_error, 6)) string += '#\t\t\n' return string
class Blockette055(Blockette): """ Blockette 055: Response List Blockette. This blockette alone is not an acceptable response description; always use this blockette along with the standard response blockettes ([53], [54], [57], or [58]). If this is the only response available, we strongly recommend that you derive the appropriate poles and zeros and include blockette 53 and blockette 58. """ id = 55 # Typo is itentional. name = "Response list" fields = [ Integer(3, "Stage sequence number", 2), Integer(4, "Stage input units", 3, xpath=34), Integer(5, "Stage output units", 3, xpath=34), Integer(6, "Number of responses listed", 4), # REPEAT fields 7 — 11 for the Number of responses listed: Loop('Response', "Number of responses listed", [ Float(7, "Frequency", 12, mask='%+1.5e'), Float(8, "Amplitude", 12, mask='%+1.5e'), Float(9, "Amplitude error", 12, mask='%+1.5e'), Float(10, "Phase angle", 12, mask='%+1.5e'), Float(11, "Phase error", 12, mask='%+1.5e') ], repeat_title=True) ] # Changes the name of the blockette because of an error in XSEED 1.0 def getXML(self, *args, **kwargs): xml = Blockette.getXML(self, *args, **kwargs) if self.xseed_version == '1.0': xml.tag = 'reponse_list' return xml def getRESP(self, station, channel, abbreviations): """ Returns RESP string. """ string = \ '#\t\t+ +---------------------------------+' +\ ' +\n' + \ '#\t\t+ | Response List,%6s ch %s |' + \ ' +\n' % (station, channel) + \ '#\t\t+ +---------------------------------+' +\ ' +\n' + \ '#\t\t\n' + \ 'B055F03 Stage sequence number: %s\n' \ % self.stage_sequence_number + \ 'B055F04 Response in units lookup: %s\n' \ % Blockette34Lookup(abbreviations, self.stage_input_units) + \ 'B055F05 Response out units lookup: %s\n' \ % Blockette34Lookup(abbreviations, self.stage_output_units) + \ 'B055F06 Number of responses: %s\n' \ % self.number_of_responses_listed if self.number_of_responses_listed: string += \ '#\t\tResponses:\n' + \ '#\t\t frequency\t amplitude\t amp error\t ' + \ 'phase\t phase error\n' if self.number_of_responses_listed > 1: for _i in range(self.number_of_responses_listed): string += 'B055F07-11 %s\t%s\t%s\t%s\t%s\n' % \ (formatRESP(self.frequency[_i], 6), formatRESP(self.amplitude[_i], 6), formatRESP(self.amplitude_error[_i], 6), formatRESP(self.phase_angle[_i], 6), formatRESP(self.phase_error[_i], 6)) else: string += 'B055F07-11 %s\t%s\t%s\t%s\t%s\n' % \ (formatRESP(self.frequency, 6), formatRESP(self.amplitude, 6), formatRESP(self.amplitude_error, 6), formatRESP(self.phase_angle, 6), formatRESP(self.phase_error, 6)) string += '#\t\t\n' return string
class Blockette054(Blockette): """ Blockette 054: Response (Coefficients) Blockette. This blockette is usually used only for finite impulse response (FIR) filter stages. You can express Laplace transforms this way, but you should use the Response (Poles & Zeros) Blockettes [53] for this. You can express IIR filters this way, but you should use the Response (Poles & Zeros) Blockette [53] here, too, to avoid numerical stability problems. Usually, you will follow this blockette with a Decimation Blockette [57] and a Sensitivity/Gain Blockette [58] to complete the definition of the filter stage. This blockette is the only blockette that might overflow the maximum allowed value of 9,999 characters. If there are more coefficients than fit in one record, list as many as will fit in the first occurrence of this blockette (the counts of Number of numerators and Number of denominators would then be set to the number included, not the total number). In the next record, put the remaining number. Be sure to write and read these blockettes in sequence, and be sure that the first few fields of both records are identical. Reading (and writing) programs have to be able to work with both blockettes as one after reading (or before writing). In July 2007, the FDSN adopted a convention that requires the coefficients to be listed in forward time order. As a reference, minimum-phase filters (which are asymmetric) should be written with the largest values near the beginning of the coefficient list. """ id = 54 name = "Response Coefficients" fields = [ FixedString(3, "Response type", 1, 'U'), Integer(4, "Stage sequence number", 2), Integer(5, "Signal input units", 3, xpath=34), Integer(6, "Signal output units", 3, xpath=34), Integer(7, "Number of numerators", 4), # REPEAT fields 8 — 9 for the Number of numerators: Loop('Numerators', "Number of numerators", [ Float(8, "Numerator coefficient", 12, mask='%+1.5e'), Float(9, "Numerator error", 12, mask='%+1.5e') ], flat=True), Integer(10, "Number of denominators", 4), # REPEAT fields 11 — 12 for the Number of denominators: Loop('Denominators', "Number of denominators", [ Float(11, "Denominator coefficient", 12, mask='%+1.5e'), Float(12, "Denominator error", 12, mask='%+1.5e') ], flat=True) ] def getRESP(self, station, channel, abbreviations): """ Returns RESP string. """ string = \ '#\t\t+ +----------------------------------------' +\ '---+ +\n' + \ '#\t\t+ | Response (Coefficients),' + \ '%6s ch %s | +\n' % (station, channel) + \ '#\t\t+ +----------------------------------------' +\ '---+ +\n' + \ '#\t\t\n' + \ 'B054F03 Transfer function type: %s\n' \ % self.response_type + \ 'B054F04 Stage sequence number: %s\n' \ % self.stage_sequence_number + \ 'B054F05 Response in units lookup: %s\n'\ % Blockette34Lookup(abbreviations, self.signal_input_units) +\ 'B054F06 Response out units lookup: %s\n'\ % Blockette34Lookup(abbreviations, self.signal_output_units) +\ 'B054F07 Number of numerators: %s\n' \ % self.number_of_numerators + \ 'B054F10 Number of denominators: %s\n' \ % self.number_of_denominators if self.number_of_numerators: string += \ '#\t\tNumerator coefficients:\n' + \ '#\t\t i, coefficient, error\n' if self.number_of_numerators > 1: # Loop over all zeros. for _i in range(self.number_of_numerators): string += 'B054F08-09 %3s %13s %13s\n' % ( _i, formatRESP(self.numerator_coefficient[_i], 6), formatRESP(self.numerator_error[_i], 6)) else: string += 'B054F08-09 %3s %13s %13s\n' % ( 0, formatRESP(self.numerator_coefficient, 6), formatRESP(self.numerator_error, 6)) if self.number_of_denominators: string += \ '#\t\tDenominator coefficients:\n' + \ '#\t\t i, coefficient, error\n' if self.number_of_denominators > 1: # Loop over all zeros. for _i in range(self.number_of_numerators): string += 'B054F11-12 %3s %13s %13s\n' % ( _i, formatRESP(self.denominator_coefficient[_i], 6), formatRESP(self.denominator_error[_i], 6)) else: string += 'B054F11-12 %3s %13s %13s\n' % ( 0, formatRESP(self.denominator_coefficient, 6), formatRESP(self.denominator_error, 6)) string += '#\t\t\n' return string.encode()
class Blockette060(Blockette): def __init__(self, *args, **kwargs): # @UnusedVariable """ """ self.stages = [] super(Blockette060, self).__init__() """ Blockette 060: Response Reference Blockette. Use this blockette whenever you want to replace blockettes [53] through [58] and [61] with their dictionary counterparts, blockettes [43] through [48] and [41]. We recommend placing responses in stage order, even if this means using more than one Response Reference Blockette [60]. Here is an example: Stage 1: Response (Poles & Zeros) Blockette [53] Channel Sensitivity/Gain Blockette [58] First response reference blockette: Response Reference Blockette [60] Stage 2: [44] [47] [48] Stage 3: [44] [47] [48] Stage 4: [44] [47] Channel Sensitivity/Gain Blockette [58] Stage 5: Response (Coefficients) Blockette [54] (End of first response reference blockette) Second response reference blockette: Response Reference Blockette [60] Stage 5 (continued): [47] [48] Stage 6: [44] [47] [48] (End of second response reference blockette) Substitute Response Reference Blockette [60] anywhere the original blockette would go, but be sure to place it in the same position as the original would have gone. (Note that this blockette uses a repeating field (response reference) within another repeating field (stage value). This is the only blockette in the current version (2.1) that has this "two dimensional" structure.) """ id = 60 name = "Response Reference Blockette" fields = [ Integer(3, "Number of stages", 2), # REPEAT field 4, with appropriate fields 5 and 6, for each stage Loop( "FIR Coefficient", "Number of stages", [ Integer(4, "Stage sequence number", 2), Integer(5, "Number of responses", 2), # REPEAT field 6, one for each response within each stage: Loop("Response lookup key", "Number of responses", [Integer(6, "Response lookup key", 4)], omit_tag=True), ]), ] def parseSEED(self, data, length=0, *args, **kwargs): # @UnusedVariable """ Read Blockette 60. """ # convert to stream for test issues if isinstance(data, bytes): length = len(data) data = io.BytesIO(data) elif isinstance(data, (str, native_str)): raise TypeError("data must be bytes, not string") new_data = data.read(length) new_data = new_data[7:] number_of_stages = int(new_data[0:2]) # Loop over all stages. counter = 2 for _i in range(number_of_stages): number_of_responses = int(new_data[counter + 2:counter + 4]) self.stages.append([]) # Start inner loop counter += 4 for _j in range(number_of_responses): # Append to last list. self.stages[-1].append(int(new_data[counter:counter + 4])) counter += 4 def getSEED(self, *args, **kwargs): # @UnusedVariable """ Writes Blockette 60. """ data = '' # Write number of stages. data += '%2d' % len(self.stages) # Loop over all items in self.stages. stage_number = 1 for stage in self.stages: # Write stage sequence number. data += '%2d' % stage_number stage_number += 1 # Write number of items. data += '%2d' % len(stage) for number in stage: data += '%4d' % number # Add header. length = len(data) + 7 header = '060%4d' % length data = header + data return data def getXML(self, xseed_version, *args, **kwargs): # @UnusedVariable """ Write XML. """ if xseed_version == '1.0': msg = 'The xsd-validation file for XML-SEED version 1.0 does ' + \ 'not support Blockette 60. It will be written but ' + \ 'please be aware that the file cannot be validated.\n' + \ 'If you want to validate your file please use XSEED ' + \ 'version 1.1.\n' sys.stdout.write(msg) node = Element('response_reference', blockette="060") SubElement(node, 'number_of_stages').text = str(len(self.stages)) # Loop over stages. for _i in range(len(self.stages)): inner_stage = SubElement(node, 'stage') SubElement(inner_stage, 'stage_sequence_number').text = str(_i + 1) SubElement(inner_stage, 'number_of_responses').text = \ str(len(self.stages[_i])) for _j in range(len(self.stages[_i])): SubElement(inner_stage, 'response_lookup_key').text = \ setXPath('dictionary', self.stages[_i][_j]) return node def parseXML(self, xml_doc, version='1.0', *args, **kwargs): # @UnusedVariable """ Read XML of blockette 60. """ # Loop over ch for child in xml_doc.getchildren(): if child.tag != 'stage': continue self.stages.append([]) for inner_child in child.getchildren(): if inner_child.tag != 'response_lookup_key': continue # for legacy support meaning XSEED without XPaths.: if inner_child.text.isdigit(): self.stages[-1].append(int(inner_child.text)) else: self.stages[-1].append(getXPath(inner_child.text)) def getRESP(self, station, channel, abbreviations): """ Returns RESP string. """ string = '' # Possible dictionary blockettes. dict_blockettes = [41, 43, 44, 45, 46, 47, 48] for _i in range(len(self.stages)): string += \ '#\t\t+ +----------------------------------' + \ '----------------+ +\n' + \ '#\t\t+ | Response Reference Information,' + \ '%6s ch %s | +\n' % (station, channel) + \ '#\t\t+ +----------------------------------' + \ '----------------+ +\n' + \ '#\t\t\n' + \ 'B060F03 Number of Stages: %s\n' \ % len(self.stages) + \ 'B060F04 Stage number: %s\n' \ % (_i + 1) + \ 'B060F05 Number of Responses: %s\n' \ % len(self.stages[_i]) + \ '#\t\t\n' # Loop over all keys and print the information in order. for response_key in self.stages[_i]: # Find the corresponding key in the abbreviations. found_abbrev = False for blockette in abbreviations: if blockette.id in dict_blockettes and \ blockette.response_lookup_key == response_key: try: string += \ blockette.getRESP(station, channel, abbreviations) found_abbrev = True except AttributeError: msg = 'RESP output not implemented for ' + \ 'blockette %d.' % blockette.id raise AttributeError(msg) if not found_abbrev: msg = 'The reference blockette for response key ' + \ '%d could not be found.' % response_key raise Exception(msg) string += '#\t\t\n' return string.encode()
class Blockette062(Blockette): """ Blockette 062: Response [Polynomial] Blockette. Use this blockette to characterize the response of a non-linear sensor. The polynomial response blockette describes the output of an Earth sensor in fundamentally a different manner than the other response blockettes. The functional describing the sensor for the polynomial response blockette will have Earth units while the independent variable of the function will be in volts. This is precisely opposite to the other response blockettes. While it is a simple matter to convert a linear response to either form, the non-linear response (which we can describe in the polynomial blockette) would require extensive curve fitting or polynomial inversion to convert from one function to the other. Most data users are interested in knowing the sensor output in Earth units, and the polynomial response blockette facilitates the access to Earth units for sensors with non-linear responses. """ id = 62 name = "Response Polynomial" fields = [ FixedString(3, "Transfer Function Type", 1), Integer(4, "Stage Sequence Number", 2), Integer(5, "Stage Signal In Units", 3, xpath=34), Integer(6, "Stage Signal Out Units", 3, xpath=34), FixedString(7, "Polynomial Approximation Type", 1), FixedString(8, "Valid Frequency Units", 1), Float(9, "Lower Valid Frequency Bound", 12, mask='%+1.5e'), Float(10, "Upper Valid Frequency Bound", 12, mask='%+1.5e'), Float(11, "Lower Bound of Approximation", 12, mask='%+1.5e'), Float(12, "Upper Bound of Approximation", 12, mask='%+1.5e'), Float(13, "Maximum Absolute Error", 12, mask='%+1.5e'), Integer(14, "Number of Polynomial Coefficients", 3), # REPEAT fields 15 and 16 for each polynomial coefficient Loop("Polynomial Coefficients", "Number of Polynomial Coefficients", [ Float(12, "Polynomial Coefficient", 12, mask='%+1.5e'), Float(12, "Polynomial Coefficient Error", 12, mask='%+1.5e'), ]) ] # Changes the name of the blockette because of an error in XSEED 1.0 def getXML(self, *args, **kwargs): xml = Blockette.getXML(self, *args, **kwargs) if self.xseed_version == '1.0': msg = 'The xsd-validation file for XML-SEED version 1.0 does ' + \ 'not support Blockette 62. It will be written but ' + \ 'please be aware that the file cannot be validated.\n' + \ 'If you want to validate your file please use XSEED ' + \ 'version 1.1.\n' sys.stdout.write(msg) return xml def getRESP(self, station, channel, abbreviations): """ Returns RESP string. """ # Field three needs some extra parsing. field_three_dict = { 'A': 'A [Laplace Transform (Rad/sec)]', 'B': 'B [Analog (Hz)]', 'C': 'C [Composite]', 'D': 'D [Digital (Z-transform)]', 'P': 'P [Polynomial]' } # Frequency too! frequency_dict = {'A': 'A [rad/sec]', 'B': 'B [Hz]'} # Polynomial Approximation too. polynomial_dict = {'M': 'M [MacLaurin]'} string = \ '#\t\t+ +-----------------------' + \ '----------------+ +\n' + \ '#\t\t+ | Polynomial response,' + \ '%6s ch %s | +\n' % (station, channel) + \ '#\t\t+ +-----------------------' + \ '----------------+ +\n' + \ '#\t\t\n' + \ 'B062F03 Transfer function type: %s\n' \ % field_three_dict[self.transfer_function_type] + \ 'B062F04 Stage sequence number: %s\n' \ % self.stage_sequence_number + \ 'B062F05 Response in units lookup: %s\n' \ % Blockette34Lookup(abbreviations, self.stage_signal_in_units) + \ 'B062F06 Response out units lookup: %s\n' \ % Blockette34Lookup(abbreviations, self.stage_signal_out_units) + \ 'B062F07 Polynomial Approximation Type: %s\n' \ % polynomial_dict[self.polynomial_approximation_type] + \ 'B062F08 Valid Frequency Units: %s\n' \ % frequency_dict[self.valid_frequency_units] + \ 'B062F09 Lower Valid Frequency Bound: %G\n' \ % self.lower_valid_frequency_bound + \ 'B062F10 Upper Valid Frequency Bound: %G\n' \ % self.upper_valid_frequency_bound + \ 'B062F11 Lower Bound of Approximation: %G\n' \ % self.lower_bound_of_approximation + \ 'B062F12 Upper Bound of Approximation: %G\n' \ % self.upper_bound_of_approximation + \ 'B062F13 Maximum Absolute Error: %G\n' \ % self.maximum_absolute_error + \ 'B062F14 Number of coefficients: %d\n' \ % self.number_of_polynomial_coefficients if self.number_of_polynomial_coefficients: string += '#\t\tPolynomial coefficients:\n' + \ '#\t\t i, coefficient, error\n' if self.number_of_polynomial_coefficients > 1: for _i in range(self.number_of_polynomial_coefficients): string += 'B062F15-16 %2s %13s %13s\n' \ % (_i, formatRESP(self.polynomial_coefficient[_i], 6), formatRESP(self.polynomial_coefficient_error[_i], 6)) else: string += 'B062F15-16 %2s %13s %13s\n' \ % (0, formatRESP(self.polynomial_coefficient, 6), formatRESP(self.polynomial_coefficient_error, 6)) string += '#\t\t\n' return string
class Blockette053(Blockette): """ Blockette 053: Response (Poles & Zeros) Blockette. Sample:: 0530382B 1007008 7.87395E+00 5.00000E-02 3 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 -1.27000E+01 0.00000E+00 0.00000E+00 0.00000E+00 4 -1.96418E-03 1.96418E-03 0.00000E+00 0.00000E+00 S-1.96418E-03-1.96418E-03 0.00000E+00 0.00000E+00 53-6.23500E+00 7.81823E+00 0.00000E+00 0.00000E+00 -6.23500E+00-7.81823E+00 0.00000E+00 0.00000E+00 """ id = 53 name = "Response Poles and Zeros" fields = [ FixedString(3, "Transfer function types", 1, 'U'), Integer(4, "Stage sequence number", 2), Integer(5, "Stage signal input units", 3, xpath=34), Integer(6, "Stage signal output units", 3, xpath=34), Float(7, "A0 normalization factor", 12, mask='%+1.5e'), Float(8, "Normalization frequency", 12, mask='%+1.5e'), Integer(9, "Number of complex zeros", 3), # REPEAT fields 10 — 13 for the Number of complex zeros: Loop('Complex zero', "Number of complex zeros", [ Float(10, "Real zero", 12, mask='%+1.5e'), Float(11, "Imaginary zero", 12, mask='%+1.5e'), Float(12, "Real zero error", 12, mask='%+1.5e'), Float(13, "Imaginary zero error", 12, mask='%+1.5e') ]), Integer(14, "Number of complex poles", 3), # REPEAT fields 15 — 18 for the Number of complex poles: Loop('Complex pole', "Number of complex poles", [ Float(15, "Real pole", 12, mask='%+1.5e'), Float(16, "Imaginary pole", 12, mask='%+1.5e'), Float(17, "Real pole error", 12, mask='%+1.5e'), Float(18, "Imaginary pole error", 12, mask='%+1.5e') ]) ] def getRESP(self, station, channel, abbreviations): """ Returns RESP string. """ # Field three needs some extra parsing. field_three_dict = { 'A': 'A [Laplace Transform (Rad/sec)]', 'B': 'B [Analog (Hz)]', 'C': 'C [Composite]', 'D': 'D [Digital (Z-transform)]' } out = RESP % ( station, channel, field_three_dict[self.transfer_function_types], self.stage_sequence_number, LookupCode(abbreviations, 34, 'unit_name', 'unit_lookup_code', self.stage_signal_input_units), LookupCode(abbreviations, 34, 'unit_description', 'unit_lookup_code', self.stage_signal_input_units), LookupCode(abbreviations, 34, 'unit_name', 'unit_lookup_code', self.stage_signal_output_units), LookupCode(abbreviations, 34, 'unit_description', 'unit_lookup_code', self.stage_signal_output_units), self.A0_normalization_factor, self.normalization_frequency, self.number_of_complex_zeros, self.number_of_complex_poles) if self.number_of_complex_zeros > 0: if self.number_of_complex_zeros != 1: # Loop over all zeros. for _i in range(self.number_of_complex_zeros): out += 'B053F10-13 %4s %13s %13s %13s %13s\n' % ( _i, formatRESP(self.real_zero[_i], 6), formatRESP(self.imaginary_zero[_i], 6), formatRESP(self.real_zero_error[_i], 6), formatRESP(self.imaginary_zero_error[_i], 6)) else: out += 'B053F10-13 %4s %13s %13s %13s %13s\n' % ( 0, formatRESP(self.real_zero, 6), formatRESP(self.imaginary_zero, 6), formatRESP(self.real_zero_error, 6), formatRESP(self.imaginary_zero_error, 6)) out += '#\t\tComplex poles:\n' out += '#\t\t i real imag real_error ' out += 'imag_error\n' if self.number_of_complex_poles > 0: if self.number_of_complex_poles != 1: # Loop over all poles. for _i in range(self.number_of_complex_poles): out += 'B053F15-18 %4s %13s %13s %13s %13s\n' % ( _i, formatRESP(self.real_pole[_i], 6), formatRESP(self.imaginary_pole[_i], 6), formatRESP(self.real_pole_error[_i], 6), formatRESP(self.imaginary_pole_error[_i], 6)) else: out += 'B053F15-18 %4s %13s %13s %13s %13s\n' % ( 0, formatRESP(self.real_pole, 6), formatRESP(self.imaginary_pole, 6), formatRESP(self.real_pole_error, 6), formatRESP(self.imaginary_pole_error, 6)) out += '#\t\t\n' return out.encode()
class Blockette048(Blockette): """ Blockette 048: Channel Sensitivity/Gain Dictionary Blockette. See Channel Sensitivity/Gain Blockette [58] for more information. """ id = 48 name = "Channel Sensivitity Gain Dictionary" fields = [ Integer(3, "Response Lookup Key", 4), VariableString(4, "Response Name", 1, 25, 'UN_'), Float(5, "Sensitivity gain", 12, mask='%+1.5e'), Float(6, "Frequency", 12, mask='%+1.5e'), Integer(7, "Number of history values", 2), # REPEAT fields 8 — 10 for the Number of history values: Loop('History', "Number of history values", [ Float(8, "Sensitivity for calibration", 12, mask='%+1.5e'), Float(9, "Frequency of calibration sensitivity", 12, mask='%+1.5e'), VariableString(10, "Time of above calibration", 1, 22, 'T') ]) ] def getRESP(self, station, channel, abbreviations): """ Returns RESP string. """ string = \ '#\t\t+ ' + \ '+---------------------------------------+' + \ ' +\n' + \ '#\t\t+ | Channel Sensitivity,' + \ '%6s ch %s | +\n' % (station, channel) + \ '#\t\t+ ' + \ '+---------------------------------------+' + \ ' +\n' + \ '#\t\t\n' + \ 'B048F05 Sensitivity: %s\n' \ % formatRESP(self.sensitivity_gain, 6) + \ 'B048F06 Frequency of sensitivity: %s\n' \ % formatRESP(self.frequency, 6) + \ 'B048F07 Number of calibrations: %s\n' \ % self.number_of_history_values if self.number_of_history_values > 1: string += \ '#\t\tCalibrations:\n' + \ '#\t\t i, sensitivity, frequency, time of calibration\n' for _i in range(self.number_of_history_values): string += \ 'B048F08-09 %2s %13s %13s %s\n' \ % (formatRESP(self.sensitivity_for_calibration[_i], 6), formatRESP( self.frequency_of_calibration_sensitivity[_i], 6), self.time_of_above_calibration[_i].formatSEED()) elif self.number_of_history_values == 1: string += \ '#\t\tCalibrations:\n' + \ '#\t\t i, sensitivity, frequency, time of calibration\n' + \ 'B048F08-09 0 %13s %13s %s\n' % ( formatRESP(self.sensitivity_for_calibration, 6), formatRESP(self.frequency_of_calibration_sensitivity, 6), self.time_of_above_calibration.formatSEED()) string += '#\t\t\n' return string