def _calc_resp(calfile, nfft, sampfreq): """ Calculate transfer function of known system. :type calfile: str :param calfile: file containing poles, zeros and scale factor for known system or a dictionary with poles and zeros information (with keys ``'poles'``, ``'zeros'`` and ``'sensitivity'``). :returns: complex transfer function, array of frequencies """ # test if calfile is a paz dict if isinstance(calfile, dict): paz = calfile # or read paz file if a filename is specified else: paz = dict() paz['poles'], paz['zeros'], paz['sensitivity'] = read_paz(calfile) # calculate transfer function h, f = paz_to_freq_resp(paz['poles'], paz['zeros'], paz['sensitivity'], 1.0 / sampfreq, nfft, freq=True) return h, f
def computeresp(resp, delta, lenfft): respval = paz_to_freq_resp(resp['poles'], resp['zeros'], resp['sensitivity'] * resp['gain'], t_samp=delta, nfft=lenfft, freq=False) respval = np.absolute(respval * np.conjugate(respval)) respval = respval[1:] return respval
def _calc_resp(calfile, nfft, sampfreq): """ Calculate transfer function of known system. :type calfile: str :param calfile: file containing poles, zeros and scale factor for known system :returns: complex transfer function, array of frequencies """ # calculate transfer function poles, zeros, scale_fac = read_paz(calfile) h, f = paz_to_freq_resp(poles, zeros, scale_fac, 1.0 / sampfreq, nfft, freq=True) return h, f
def computeresp(resp, delta, lenfft): respval, freq = paz_to_freq_resp(resp['poles'], resp['zeros'], resp['sensitivity'] * resp['gain'], t_samp=delta, nfft=lenfft, freq=True) idx = np.argmin(np.abs(freq - .1)) respval = np.absolute(respval * np.conjugate(respval)) respval *= 1. / respval[idx] respval = respval[1:] * resp['sensitivity']**2 respval = respval.real return respval
def test_simple_response(): import matplotlib.pyplot as plt try: from obspy.signal.invsim import paz_to_freq_resp except: from obspy.signal.invsim import pazToFreqResp as paz_to_freq_resp try: from obspy.signal.invsim import paz_2_amplitude_value_of_freq_resp except: from obspy.signal.invsim import paz2AmpValueOfFreqResp as paz_2_amplitude_value_of_freq_resp #{'zeros': [(-31.617+0j), 0j, 0j], 'poles': [(-0.1486-0.1486j), (-0.1486+0.1486j), (-336.766-136.656j), (-336.766+136.656j), (-47.0636+0j), (-2469.36+0j)], 'gain': 491884000.0} N = 1024 sample_rate = 100. normalization_frequency = 1.0 normalization_factor = 1.6 poles = [(-5.026548 + 3.769911j), (-5.026548 - 3.769911j)] zeros = [complex(0.), complex(0.)] amplitude, frequency = paz_to_freq_resp(poles, zeros, 1.0, 1.0 / sample_rate, 2 * N, freq=True) print len(amplitude) print np.max(amplitude) plt.loglog(frequency, abs(amplitude), basex=10, basey=10) plt.show() paz = {"poles": poles, "zeros": zeros, "gain": normalization_factor} calculated_A0 = paz_2_amplitude_value_of_freq_resp( paz, normalization_frequency) if abs(calculated_A0 - 1.0) > 1e-06: print "Discrepancy, calculated normalized amplitude: {}, \ normalization factor {} at normalization frequency {}\ does not normalize the frequency spectrum." .\ format(calculated_A0, normalization_factor, normalization_frequency)
inst1 = Response(desc=instName, units='Radians') inst1.zeros = zer1 inst1.poles = pol1 #norm_freq=0.02 norm_freq = 1.0 n1, f1 = inst1.check_normalization(freq=norm_freq, nfft=2**26, t_sample=0.001) scale_fac = 1.0 / n1 print('The A0 norm factor is: ' + str(scale_fac) + ' for f=' + str(norm_freq)) #check the value inst1.a0 = 1.0 / n1 A01 = inst1.a0 n, f = inst1.check_normalization(freq=norm_freq, nfft=2**26, t_sample=0.001) print('This should be close to 1: ' + str(1.0 / n)) h1, f1 = paz_to_freq_resp(inst1.poles, inst1.zeros, scale_fac, 0.001, 2**26, freq=True) print(h1) # and now for the second resp.... inst2 = Response(desc=instName, units='Radians') inst2.zeros = zer2 inst2.poles = pol2 #norm_freq=0.02 norm_freq = 1.0 n2, f2 = inst2.check_normalization(freq=norm_freq, nfft=2**24, t_sample=0.001) scale_fac = 1.0 / n2 print('The A0 norm factor is: ' + str(scale_fac) + ' for f=' + str(norm_freq)) #check the value inst2.a0 = 1.0 / n2
inst.poles = pol #norm_freq=0.05 #norm_freq=0.02 norm_freq = 1.0 n, f = inst.check_normalization(freq=norm_freq, nfft=2**26, t_sample=0.001) scale_fac = 1.0 / n print('The A0 norm factor is: ' + str(scale_fac) + ' for f=' + str(norm_freq)) #check the value inst.a0 = 1.0 / n n, f = inst.check_normalization(freq=norm_freq, nfft=2**26, t_sample=0.001) print('This should be close to 1: ' + str(1.0 / n)) h, f = paz_to_freq_resp(inst.poles, inst.zeros, scale_fac, 0.001, 2**26, freq=True) plt.figure() plt.subplot(121) plt.semilogx(f, abs(h)) plt.xlabel('Frequency [Hz]') plt.ylabel('Amplitude') plt.grid(which='both', linestyle=':') #plt.xlim([0.2,40]) plt.subplot(122) # take negative of imaginary part phase = np.unwrap(np.arctan2(h.imag, h.real)) plt.semilogx(f, phase)
import numpy as np import matplotlib.pyplot as plt from obspy.signal.invsim import paz_to_freq_resp poles = [-4.440 + 4.440j, -4.440 - 4.440j, -1.083 + 0.0j] zeros = [0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j] scale_fac = 0.4 h, f = paz_to_freq_resp(poles, zeros, scale_fac, 0.005, 16384, freq=True) plt.figure() plt.subplot(121) plt.loglog(f, abs(h)) plt.xlabel('Frequency [Hz]') plt.ylabel('Amplitude') plt.subplot(122) # take negative of imaginary part phase = np.unwrap(np.arctan2(-h.imag, h.real)) plt.semilogx(f, phase) plt.xlabel('Frequency [Hz]') plt.ylabel('Phase [radian]') # title, centered above both subplots plt.suptitle('Frequency Response of LE-3D/1s Seismometer') # make more room in between subplots for the ylabel of right plot plt.subplots_adjust(wspace=0.3) plt.show()
def plot_Freq_Amp_Phase(pltfile, paz, sName, type): poles = paz['poles'] zeros = paz['zeros'] scale_fac = paz['gain'] * paz['seismometer_gain'] if (1000 <= type and type < 2000): h, f = paz_to_freq_resp(poles, zeros, scale_fac, 0.005, 65536 * 8, freq=True) elif (2000 <= type): h, f = paz_to_freq_resp(poles, zeros, scale_fac, 0.001, 65536 * 8, freq=True) else: h, f = paz_to_freq_resp(poles, zeros, scale_fac, 0.001, 65536 * 8, freq=True) font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=12) fontTitle = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=18) plt.figure(figsize=(16, 12)) # plt.xlim(0.001, 100) plt.subplot(211) plt.grid(linestyle=':') plt.loglog(f, abs(h)) plt.xlabel('频率[Hz]', fontproperties=font) if 1000 <= type < 2000: plt.ylabel('增益[V/(m/s)]', fontproperties=font) elif 2000 <= type: plt.ylabel('增益[V/(m/s**2)]', fontproperties=font) else: plt.ylabel('增益', fontproperties=font) plt.subplot(212) plt.grid(linestyle=':') phase = np.unwrap(np.angle(h)) fTt = 0 for p0 in phase: fTt += p0 fAver = fTt / len(phase) if (fAver <= -np.pi): phase += 2 * np.pi elif (fAver > np.pi): phase -= 2 * np.pi import datetime timestr = datetime.datetime.now().strftime('%Y-%m-%d') timestr = "Created by 泰德, " + timestr x0 = ' 频率[Hz] ' + timestr plt.semilogx(f, phase) # plt.xlabel('频率[Hz]',fontproperties=font) plt.xlabel(x0, fontproperties=font) plt.ylabel('相位[rad]', fontproperties=font) # ticks and tick labels at multiples of pi plt.yticks( [-np.pi, -np.pi / 2, 0, np.pi / 2, np.pi], [r'$-\pi$', r'$-\frac{\pi}{2}$', '$0$', r'$\frac{\pi}{2}$', r'$\pi$']) plt.ylim(-0.2 - np.pi, np.pi + 0.2) # title, centered above both subplots if (1000 <= type and type < 2000): plt.suptitle('%s 地震计幅频相频响应' % (sName), fontproperties=fontTitle) elif (2000 <= type): plt.suptitle('%s 加速度计幅频相频响应' % (sName), fontproperties=fontTitle) else: plt.suptitle('仪器 %s 幅频相频响应' % (sName), fontproperties=fontTitle) # import datetime # timestr = datetime.datetime.now().strftime('%Y-%m-%d') # timestr = "Created by TAIDE, " + timestr # plt.text(10.6,-4.2,timestr,fontsize=12) # make more room in between subplots for the ylabel of right plot plt.subplots_adjust(wspace=0.1) plt.savefig(pltfile) plt.close('all')
# We can do the same thing for an STS-1 response paz2 = { 'gain': 3948.58, 'zeros': [0, 0], 'poles': [ -0.01234 - 0.01234j, -0.01234 + 0.01234j, -39.18 - 49.12j, -39.18 + 49.12j ], 'sensitivity': 3.3554432 * 10**9 } respval = paz_to_freq_resp(paz2['poles'], paz2['zeros'], paz2['sensitivity'], t_samp=1. / sampling_rate, nfft=lenfft, freq=False) respval = np.absolute(respval * np.conjugate(respval)) respval = respval[1:] respval = respval / respval[1] AerrSTS1 = 10. * np.log10(((AS * AE * (respval))**2) / freq) fig = plt.figure(1) plt.subplot(2, 1, 1) plt.semilogx(perNLNM, NLNM, label='NLNM/NHNM', color='.7', linewidth=2.) plt.semilogx(perNHNM, NHNM, color='.7', linewidth=2.) plt.semilogx(1. / freq, A, label='Brune Spectra Mw=' + str(Mw) + ' at ' + str(Rrs / 1000.) +
paz2['gain'] = 1. / paz_2_amplitude_value_of_freq_resp(paz2, .1) # this is from sensor test suite paz3 = { 'zeros': [0.000000E+00 + 0.000000E+00j, 0.000000E+00 + 0.000000E+00j], 'poles': [ -1.263000E-02 + 1.265000E-02j, -1.263000E-02 + -1.265000E-02j, -3.620107E+01 + 6.850121E+01j, -3.620107E+01 + -6.850121E+01j ], 'gain': 1 } paz3['gain'] = 1. / paz_2_amplitude_value_of_freq_resp(paz3, .1) h1, f1 = paz_to_freq_resp(paz1['poles'], paz1['zeros'], paz1['gain'], 1. / 200., 2**18, freq=True) h2, f2 = paz_to_freq_resp(paz2['poles'], paz2['zeros'], paz2['gain'], 1. / 200., 2**18, freq=True) h3, f3 = paz_to_freq_resp(paz3['poles'], paz3['zeros'], paz3['gain'], 1. / 200., 2**18, freq=True) #plotting....
def _get_response_from_paz_dict(self, tr): paz = self.metadata resp = paz_to_freq_resp(paz['poles'], paz['zeros'], paz['gain'] * paz['sensitivity'], self.delta, nfft=self.nfft) return resp
norm_freq = 1.0 #define the poles and zeros. #tr-120 NRL paz pol = [-32.55, -142, -364 + 404j, -364 - 404j] zer = [-31.63, -350] instName = 'TR120 NRL high-f paz' inst = Response(desc=instName, units='Radians') inst.zeros = zer inst.poles = pol n, f = inst.check_normalization(freq=norm_freq, nfft=2**26, t_sample=0.001) scale_fac = 1.0 / n h1, f1 = paz_to_freq_resp(inst.poles, inst.zeros, scale_fac, 0.001, 2**26, freq=True) #tr-120 one high-f cal signal pol = [-28.15385, -146.36053, -313.52396 - 429.04933j, -313.52396 + 429.04933j] zer = [-27.63939, -467.15426] instName = 'TR120 one high-f paz' inst = Response(desc=instName, units='Radians') inst.zeros = zer inst.poles = pol n, f = inst.check_normalization(freq=norm_freq, nfft=2**26, t_sample=0.001) scale_fac = 1.0 / n h2, f2 = paz_to_freq_resp(inst.poles, inst.zeros, scale_fac,
def simple_response(sample_rate, response): """ Given the obspy ResponseStages, calculate the simple response. i.e. the natural frequency, damping factor, low corner, high corner, and overall gain """ EPSILON = 5e-02 # tolerance for normalized amplitude of a pole-zero stage being off from 1.0 NFREQ = 2048 # number of frequency points to calculate amplitude spectrum for. delta_t = 1.0 / sample_rate poles = [] zeros = [] # take the normalization frequency from the InstrumentSensitivity normalization_frequency = response.instrument_sensitivity.frequency signal_input_units = response.instrument_sensitivity.input_units # Gather all the poles and zeros, and the stage gains. total_gain = 1 for stage in response.response_stages: if hasattr(stage, "stage_gain"): stage_gain = stage.stage_gain if hasattr(stage, "zeros"): zeros.extend(stage.zeros) if hasattr(stage, "poles"): poles.extend(stage.poles) # check if normalization factor is correct paz = { "poles": stage.poles, "zeros": stage.zeros, "gain": stage.normalization_factor } calculated_amplitude = paz_2_amplitude_value_of_freq_resp( paz, stage.normalization_frequency) if np.abs(calculated_amplitude - 1.0) > EPSILON: logging.warning("Warning: normalized amplitude at normalization frequency is {}, i.e. {:6.3f}% from 1,\ using calculated gain value {:5.2f} vs {:5.2f} instead" .\ format(calculated_amplitude, 100*(calculated_amplitude-1)/1,calculated_amplitude*stage.stage_gain, stage.stage_gain)) stage_gain = calculated_amplitude * stage.stage_gain total_gain = total_gain * stage_gain # log a warning if the total_gain is not similar to the reported instrument_sensitivity if np.abs((total_gain - response.instrument_sensitivity.value) / response.instrument_sensitivity.value) > EPSILON: logging.warning("Warning: Reported sensitivity: {:5.2f}, \ Calculated sensitivity: {:5.2f}" . \ format(response.instrument_sensitivity.value, total_gain)) # calculate overall normalization factor at normalization frequency paz = {"poles": poles, "zeros": zeros, "gain": 1.0} calculated_amplitude = paz_2_amplitude_value_of_freq_resp( paz, normalization_frequency) normalization_factor = 1.0 / calculated_amplitude # calculate the normalized frequency spectrum at NFREQ frequency points amplitude, frequency = paz_to_freq_resp(poles, zeros, normalization_factor, delta_t, 2 * NFREQ, freq=True) # determine the high and low frequency corners from the amplitude spectrum logging.debug("Determining frequency corners") f_hp, f_lp = compute_corners(amplitude, frequency) logging.debug(("f_hp: {}, f_lp: {}").format(f_hp, f_lp)) # velocity transducer or acceleration? if signal_input_units == "M/S" or signal_input_units == "M": sensor_type = "VEL" else: sensor_type = "ACC" # try to determine natural frequency and damping factor from poles and corners logging.debug("Determining natural frequency and damping factor") natural_frequency, damping = natural_frequency_and_damping( poles, f_hp, f_lp, sensor_type=sensor_type) logging.debug(("fn: {}, damp: {}").format(natural_frequency, damping)) # some sanity checks, limit f_lp to 40% of Nyquist, f_hp must be <= f_lp if f_lp > 0.4 * sample_rate: f_lp = 0.4 * sample_rate if f_hp > f_lp: f_hp = f_lp return natural_frequency, damping, f_hp, f_lp, total_gain
sens = [] for sta in stas: f = open('Results_Swept_Sine' + sta, 'r') for line in f: line = line.split(',') fs.append(float(line[4])) hs.append(float(line[5])) sens.append(float(line[6])) chans.append(line[0]) f.close() #####################################################Make mean for triple in zip(fs, hs, sens): paz = corn_freq_2_paz(triple[0], triple[1]) h, f = paz_to_freq_resp(paz['poles'], paz['zeros'], 1., 1. / 1000., 2**14, True) h *= triple[2] if 'hmean' not in vars(): hmean = h else: hmean += h hmean *= 1. / 9. fig = plt.figure(1, figsize=(12, 12)) ax = fig.add_subplot(111) plt.subplots_adjust(hspace=0.001) for idx, quad in enumerate(zip(chans, fs, hs, sens)): if quad[0] == 'HHZ': color = 'C0' elif quad[0] == 'HHN':
f, p2 = signal.welch(tr.data, fs=tr.stats.sampling_rate, nperseg=NFFT, noverlap=1024) f = f[1:] p2 = p2[1:] if 'p' in vars(): p = np.vstack((p, p2)) else: p = p2 p = np.mean(p, axis=0) Tfnom, fnom = paz_to_freq_resp(sts6paz.poles, sts6paz.zeros, sts6paz.stage_gain * sts6paz.normalization_factor, 1. / st[0].stats.sampling_rate, NFFT, freq=True) Tfnom = Tfnom[1:] * (2**26) / 40. plt.semilogx(1. / f, 10. * np.log10(p / np.abs(Tfnom)**2), label='Q330HR Noise with STS-6 Response') Tfnom, fnom = paz_to_freq_resp(sts2paz.poles, sts2paz.zeros, sts2paz.stage_gain * sts2paz.normalization_factor, 1. / st[0].stats.sampling_rate, NFFT, freq=True) Tfnom = Tfnom[1:] * (2**26) / 40.
# create list of stations stationList = list(set([tr.stats.station for tr in st.traces])) # define PAZ values for transfer function # example with Lennartz 3D/5s instruments LEpoles = [(-0.888 + 0.888j), (-0.888 - 0.888j), (-0.29 + 0j)] LEzeros = [0j, 0j, 0j] LEscale = 400.0 sampfreq = 100. nfft = 4096 # maybe 4096 (adjust also in psd and csd) to get more long period spectrum # calculate transfer function T1 = paz_to_freq_resp(LEpoles, LEzeros, LEscale, 1.0 / sampfreq, nfft, freq=True) # iterate over each station combination for stat1 in stationList: for stat2 in stationList: if stat1 == stat2: continue # select traces st1 = st.select(station=stat1) st2 = st.select(station=stat2) # calculate power spectral density and cross-spectrum